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

Commit

Permalink
Start adding full plugin
Browse files Browse the repository at this point in the history
  • Loading branch information
ptgott committed Jul 8, 2024
1 parent a63d5ad commit f2ce3ab
Show file tree
Hide file tree
Showing 3 changed files with 99 additions and 5 deletions.
8 changes: 8 additions & 0 deletions server/fixtures/toc/source.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
title: My Page
description: Sample page for testing
---

Here is an intro.

(!toc database-access!)
54 changes: 54 additions & 0 deletions server/remark-toc.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import * as nodeFS from "fs";
import * as path from "path";
import matter from "gray-matter";
import { visitParents } from "unist-util-visit-parents";

// 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
Expand Down Expand Up @@ -72,3 +73,56 @@ export const resolveTOCIncludes = (dirPath: string, fs = nodeFS) => {
entries.sort();
return entries.join("\n");
};

const tocRegexp = new RegExp(`^\(!toc ([^!]+)!)$`);

export default function remarkIncludes({}: RemarkIncludesOptions = {}): Transformer {
return (root: Content, vfile: VFile) => {
const lastErrorIndex = vfile.messages.length;

visitParents(root, [isInclude], (node, ancestors: Parent[]) => {
if (node.type !== "text") {
return;
}
const parent = ancestors[ancestors.length - 1];

if (parent.type !== "paragraph") {
return;
}
if (!parent.children || parent.children.length === 1) {
return;
}

if (tocRegexp.test(node.value.trim())) {
return;
}
// TODO: rename resolveTocIncludes to reflect function signature (takes
// one dir path)
// TODO: call resolveTOCIncludes for each instance of the expression
// TODO: get dirPath
const { result, error } = resolveTOCIncludes(dirPath);

const path = node.value.match(tocRegexp)[1];

if (resolve) {
if (path.split(" ")[0].match(/\.mdx?$/)) {
const tree = fromMarkdown(result, {
extensions: [mdxjs(), gfm(), frontmatter()],
mdastExtensions: [
mdxFromMarkdown(),
gfmFromMarkdown(),
frontmatterFromMarkdown(["yaml"]),
],
});

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

grandParent.children.splice(parentIndex, 1, ...tree.children);
} else {
node.value = result;
}
}
});
};
}
42 changes: 37 additions & 5 deletions uvu-tests/remark-toc.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import { Volume, createFsFromVolume } from "memfs";
import { resolveTOCIncludes } from "../server/remark-toc";
import { remarkTOC, resolveTOCIncludes } from "../server/remark-toc";
import { suite } from "uvu";
import * as assert from "uvu/assert";
import { VFile, VFileOptions } from "vfile";
import remarkMdx from "remark-mdx";
import remarkGFM from "remark-gfm";

const Suite = suite("server/remark-toc");

Expand Down Expand Up @@ -48,7 +51,7 @@ description: "Protecting App 2 with Teleport"
---`,
};

Suite("one link to a directory", () => {
Suite("resolveTOCIncludes with one link to a directory", () => {
const expected = `- [Application Access](application-access/application-access.mdx) (section): Guides related to Application Access`;

const vol = Volume.fromJSON({
Expand Down Expand Up @@ -80,7 +83,7 @@ description: "Protecting App 2 with Teleport"
assert.equal(actual, expected);
});

Suite("multiple links to directories", () => {
Suite("resolveTOCIncludes with multiple links to directories", () => {
const expected = `- [Application Access](application-access/application-access.mdx) (section): Guides related to Application Access
- [Database Access](database-access/database-access.mdx) (section): Guides related to Database Access.`;

Expand All @@ -91,7 +94,7 @@ Suite("multiple links to directories", () => {
});

Suite(
`throws an error on a generated menu page that does not correspond to a subdirectory`,
`resolveTOCIncludes throws an error on a generated menu page that does not correspond to a subdirectory`,
() => {
const vol = Volume.fromJSON({
"/docs/docs.mdx": `---
Expand Down Expand Up @@ -125,7 +128,7 @@ description: "Guides related to JWTs"
}
);

Suite.only("orders sections correctly", () => {
Suite.only("resolveTOCIncludes orders sections correctly", () => {
const expected = `- [API Usage](api.mdx): Using the API.
- [Application Access](application-access/application-access.mdx) (section): Guides related to Application Access
- [Desktop Access](desktop-access/desktop-access.mdx) (section): Guides related to Desktop Access
Expand Down Expand Up @@ -182,4 +185,33 @@ description: "Using the API."
assert.equal(actual, expected);
});

const transformer = (vfileOptions: VFileOptions) => {
const file = new VFile(vfileOptions);

return remark()
.use(remarkMdx)
.use(remarkGFM)
.use(remarkTOC)
.processSync(file);
};

Suite("Fixture match result on resolve", () => {
const value = readFileSync(
resolve("server/fixtures/toc/source.mdx"),
"utf-8"
);

const result = transformer({
value,
path: "/content/4.0/docs/pages/filename.mdx",
}).toString();

const expected = readFileSync(
resolve("server/fixtures/toc/expected.mdx"),
"utf-8"
);

assert.equal(result, expected);
});

Suite.run();

0 comments on commit f2ce3ab

Please sign in to comment.