Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adapt DocCardList for the Teleport docs site #86

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 7 additions & 77 deletions server/remark-toc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,76 +8,6 @@ import type { VFile } from "vfile";
import type { Root, Content } from "mdast";
import type { Transformer } from "unified";

// relativePathToFile takes a filepath and returns a path we can use in links
// to the file in a table of contents page. The link path is a relative path
// to the directory where we are placing the table of contents page.
// @param root {string} - the directory path to the table of contents page.
// @param filepath {string} - the path from which to generate a link path.
const relativePathToFile = (root: string, filepath: string) => {
// Return the filepath without the first segment, removing the first
// slash. This is because the TOC file we are generating is located at
// root.
return filepath.slice(root.length).replace(/^\//, "");
};

// getTOC generates a list of links to all files in the same directory as
// filePath except for filePath. The return value is an object with two
// properties:
// - result: a string containing the resulting list of links.
// - error: an error message encountered during processing
export const getTOC = (filePath: string, fs: any = nodeFS) => {
const dirPath = path.dirname(filePath);
if (!fs.existsSync(dirPath)) {
return {
error: `Cannot generate a table of contents for nonexistent directory at ${dirPath}`,
};
}

const { name } = path.parse(filePath);

const files = fs.readdirSync(dirPath, "utf8");
let mdxFiles = new Set();
const dirs = files.reduce((accum, current) => {
// Don't add a TOC entry for the current file.
if (name == path.parse(current).name) {
return accum;
}
const stats = fs.statSync(path.join(dirPath, current));
if (!stats.isDirectory() && current.endsWith(".mdx")) {
mdxFiles.add(path.join(dirPath, current));
return accum;
}
accum.add(path.join(dirPath, current));
return accum;
}, new Set());

// Add rows to the menu page for non-menu pages.
const entries = [];
mdxFiles.forEach((f: string, idx: number) => {
const text = fs.readFileSync(f, "utf8");
let relPath = relativePathToFile(dirPath, f);
const { data } = matter(text);
entries.push(`- [${data.title}](${relPath}): ${data.description}`);
});

// Add rows to the menu page for first-level child menu pages
dirs.forEach((f: string, idx: number) => {
const menuPath = path.join(f, path.parse(f).base + ".mdx");
if (!fs.existsSync(menuPath)) {
return {
error: `there must be a page called ${menuPath} that introduces ${f}`,
};
}
const text = fs.readFileSync(menuPath, "utf8");
let relPath = relativePathToFile(dirPath, menuPath);
const { data } = matter(text);

entries.push(`- [${data.title}](${relPath}): ${data.description}`);
});
entries.sort();
return { result: entries.join("\n") };
};

const tocRegexpPattern = "^\\(!toc!\\)$";

// remarkTOC replaces (!toc!) syntax in a page with a list of docs pages at a
Expand All @@ -104,17 +34,17 @@ export default function remarkTOC(): Transformer {
return;
}

const { result, error } = getTOC(vfile.path);
if (!!error) {
vfile.message(error, node);
return;
}
const tree = fromMarkdown(result, {});
const tree = {
type: "mdxJsxFlowElement",
name: "DocCardList",
attributes: [],
children: [],
};

const grandParent = ancestors[ancestors.length - 2] as Parent;
const parentIndex = grandParent.children.indexOf(parent);

grandParent.children.splice(parentIndex, 1, ...(tree as Root).children);
grandParent.children.splice(parentIndex, 1, tree);
});
};
}
68 changes: 68 additions & 0 deletions src/theme/DocCardList/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import React from "react";
import clsx from "clsx";
import {
useCurrentSidebarCategory,
filterDocCardListItems,
useDocById,
} from "@docusaurus/plugin-content-docs/client";
import DocCard from "@theme/DocCard";
import type { Props } from "@theme/DocCardList";

function DocCardListForCurrentSidebarCategory({ className }: Props) {
const category = useCurrentSidebarCategory();
return <DocCardList items={category.items} className={className} />;
}

// categoryHrefoToDocID returns the Docusaurus page ID that corresponds to the
// given href. Category pages do not have IDs in the items prop, so we generate
// a page ID based on the assumption that category page slugs are the same as
// their containing directory names.
function categoryHrefToDocID(href: string): string {
if (!href) {
return href;
}
const idPrefix = href.replace(new RegExp(`^/ver/[0-9]+\\.x/`), "");
const slugRE = new RegExp(`/([^/]+)/$`);
const slug = slugRE.exec(href);
if (!slug || slug.length != 2) {
return "";
}
return idPrefix + slug[1];
}

export default function DocCardList(props: Props): JSX.Element {
const { items, className } = props;
if (!items) {
return <DocCardListForCurrentSidebarCategory {...props} />;
}
const filteredItems = filterDocCardListItems(items).map((item) => {
const doc = useDocById(item.docId ?? undefined);

if (item.type == "link") {
return {
href: item.href,
label: item.label,
description: doc?.description,
};
}
if (item.type == "category") {
const indexPage = useDocById(categoryHrefToDocID(item.href) ?? undefined);

return {
href: item.href,
label: item.label + " (section)",
description: indexPage?.description,
};
}
});

return (
<ul className={clsx("row", className)}>
{filteredItems.map((item, index) => (
<li key={index}>
<a href={item.href}>{item.label}</a>: {item.description}
</li>
))}
</ul>
);
}
2 changes: 2 additions & 0 deletions src/theme/MDXComponents/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import OriginalMDXComponents from "@theme-original/MDXComponents";
import Tabs from "@theme/Tabs";
import TabItem from "@theme/TabItem";
import DocCardList from "@theme/DocCardList";
import Admonition from "@theme/Admonition";
import React, { type ComponentProps } from "react";
import Head from "@docusaurus/Head";
Expand All @@ -21,6 +22,7 @@ import type { MDXComponentsObject } from "@theme/MDXComponents";
const MDXComponents: MDXComponentsObject = {
...OriginalMDXComponents,
Details: MDXDetails,
DocCardList: DocCardList,
Head,
TabItem,
Tabs,
Expand Down
Loading