Skip to content
This repository has been archived by the owner on Jan 8, 2025. It is now read-only.

Commit

Permalink
Generate sidebar entries at an arbitrary depth
Browse files Browse the repository at this point in the history
Currently, the code that generates the navigation sidebar from a
directory tree stops at the second level of a given top-level section.
However, some sections include three levels of content. This change
edits the sidebar generator so it works recursively.
  • Loading branch information
ptgott committed Jul 17, 2024
1 parent 229d29d commit f45f968
Show file tree
Hide file tree
Showing 2 changed files with 109 additions and 79 deletions.
69 changes: 26 additions & 43 deletions server/pages-helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ export const getPageInfo = <T = MDXPageFrontmatter>(
return result;
};

// getEntryForPath returns a navigation item for the file at filePath in the
// given filesystem.
const getEntryForPath = (fs, filePath) => {
const txt = fs.readFileSync(filePath, "utf8");
const { data } = matter(txt);
Expand Down Expand Up @@ -108,23 +110,36 @@ const categoryPagePathForDir = (fs, dirPath) => {
);
};

export const generateNavPaths = (fs, dirPath) => {
export const navEntriesForDir = (fs, dirPath) => {
const firstLvl = fs.readdirSync(dirPath, "utf8");
let result = [];
let firstLvlFiles = new Set();
let firstLvlDirs = new Set();

// Sort the contents of dirPath into files and directoreis.
firstLvl.forEach((p) => {
const fullPath = join(dirPath, p);
const info = fs.statSync(fullPath);
if (info.isDirectory()) {
firstLvlDirs.add(fullPath);
return;
}
const fileName = parse(fullPath).name;
const dirName = parse(dirPath).name;

// This is a category page for the containing directory. We would have
// already handled this in the previous iteration. The first iteration
// does not require a category page.
if (fileName == dirName) {
return;
}

firstLvlFiles.add(fullPath);
});

// Map category pages to the directories they introduce so we can can add a
// sidebar entry for the category page, then traverse the directory.
// sidebar entry for each category page, then traverse each directory to add
// further sidebar pages.
let sectionIntros = new Map();
firstLvlDirs.forEach((d: string) => {
sectionIntros.set(categoryPagePathForDir(fs, d), d);
Expand All @@ -145,56 +160,24 @@ export const generateNavPaths = (fs, dirPath) => {
result.push(getEntryForPath(fs, f));
});

// Add a category page for each section intro, then traverse the contents of
// the directory that the category page introduces, adding the contents to
// entries.
sectionIntros.forEach((dirPath, categoryPagePath) => {
const { slug, title } = getEntryForPath(fs, categoryPagePath);
const section = {
title: title,
slug: slug,
entries: [],
};
const secondLvl = new Set(fs.readdirSync(dirPath, "utf8"));

// Find all second-level category pages first so we don't
// repeat them in the sidebar.
secondLvl.forEach((f2: string) => {
let fullPath2 = join(dirPath, f2);
const stat = fs.statSync(fullPath2);

// List category pages on the second level, but not their contents.
if (!stat.isDirectory()) {
return;
}
const catPath = categoryPagePathForDir(fs, fullPath2);
fullPath2 = catPath;
secondLvl.delete(f2);

// Delete the category page from the set so we don't add it again
// when we add individual files.
secondLvl.delete(parse(catPath).base);
section.entries.push(getEntryForPath(fs, fullPath2));
});

secondLvl.forEach((f2: string) => {
// Only add entries for MDX files here
if (!f2.endsWith(".mdx")) {
return;
}

let fullPath2 = join(dirPath, f2);

// This is a first-level category page that happens to exist on the second
// level.
if (sectionIntros.has(fullPath2)) {
return;
}

const stat = fs.statSync(fullPath2);
section.entries.push(getEntryForPath(fs, fullPath2));
});

section.entries.sort(sortByTitle);

section.entries = navEntriesForDir(fs, dirPath);
result.push(section);
});
result.sort(sortByTitle);
return result;
};

export const generateNavPaths = (fs, dirPath) => {
return navEntriesForDir(fs, dirPath);
};
119 changes: 83 additions & 36 deletions uvu-tests/config-docs.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -289,54 +289,57 @@ title: MySQL Guide
}
);

Suite(
"generateNavPaths shows third-level category pages on the sidebar",
() => {
const files = {
"/docs/pages/database-access/guides/guides.mdx": `---
Suite("generateNavPaths shows third-level pages on the sidebar", () => {
const files = {
"/docs/pages/database-access/guides/guides.mdx": `---
title: Database Access Guides
---`,
"/docs/pages/database-access/guides/postgres.mdx": `---
"/docs/pages/database-access/guides/postgres.mdx": `---
title: Postgres Guide
---`,
"/docs/pages/database-access/guides/mysql.mdx": `---
"/docs/pages/database-access/guides/mysql.mdx": `---
title: MySQL Guide
---`,
"/docs/pages/database-access/guides/rbac/rbac.mdx": `---
"/docs/pages/database-access/guides/rbac/rbac.mdx": `---
title: Database Access RBAC
---`,
"/docs/pages/database-access/guides/rbac/get-started.mdx": `---
"/docs/pages/database-access/guides/rbac/get-started.mdx": `---
title: Get Started with DB RBAC
---`,
};
};

const expected = [
{
title: "Database Access Guides",
slug: "/database-access/guides/guides/",
entries: [
{
title: "Database Access RBAC",
slug: "/database-access/guides/rbac/rbac/",
},
{
title: "MySQL Guide",
slug: "/database-access/guides/mysql/",
},
{
title: "Postgres Guide",
slug: "/database-access/guides/postgres/",
},
],
},
];
const expected = [
{
title: "Database Access Guides",
slug: "/database-access/guides/guides/",
entries: [
{
title: "Database Access RBAC",
slug: "/database-access/guides/rbac/rbac/",
entries: [
{
title: "Get Started with DB RBAC",
slug: "/database-access/guides/rbac/get-started/",
},
],
},
{
title: "MySQL Guide",
slug: "/database-access/guides/mysql/",
},
{
title: "Postgres Guide",
slug: "/database-access/guides/postgres/",
},
],
},
];

const vol = Volume.fromJSON(files);
const fs = createFsFromVolume(vol);
const actual = generateNavPaths(fs, "/docs/pages/database-access");
assert.equal(actual, expected);
}
);
const vol = Volume.fromJSON(files);
const fs = createFsFromVolume(vol);
const actual = generateNavPaths(fs, "/docs/pages/database-access");
assert.equal(actual, expected);
});

Suite(
"allows category pages in the same directory as the associated subdirectory",
Expand Down Expand Up @@ -367,6 +370,12 @@ title: Get Started with DB RBAC
{
title: "Database Access RBAC",
slug: "/database-access/guides/rbac/",
entries: [
{
title: "Get Started with DB RBAC",
slug: "/database-access/guides/rbac/get-started/",
},
],
},
{
title: "MySQL Guide",
Expand All @@ -387,4 +396,42 @@ title: Get Started with DB RBAC
}
);

Suite("generates four levels of the sidebar", () => {
const files = {
"/docs/pages/database-access/guides/guides.mdx": `---
title: Database Access Guides
---`,
"/docs/pages/database-access/guides/deployment/kubernetes.mdx": `---
title: Database Access Kubernetes Deployment
---`,
"/docs/pages/database-access/guides/deployment/deployment.mdx": `---
title: Database Access Deployment Guides
---`,
};

const expected = [
{
title: "Database Access Guides",
slug: "/database-access/guides/guides/",
entries: [
{
title: "Database Access Deployment Guides",
slug: "/database-access/guides/deployment/deployment/",
entries: [
{
title: "Database Access Kubernetes Deployment",
slug: "/database-access/guides/deployment/kubernetes/",
},
],
},
],
},
];

const vol = Volume.fromJSON(files);
const fs = createFsFromVolume(vol);
let actual = generateNavPaths(fs, "/docs/pages/database-access");
assert.equal(actual, expected);
});

Suite.run();

0 comments on commit f45f968

Please sign in to comment.