-
Notifications
You must be signed in to change notification settings - Fork 42
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
Markdown-driven static pages: building the framework #5601
Changes from 21 commits
fc25891
aa4a131
390d2cf
43d1a5d
05b017c
89d1301
8ce17fc
48cf243
f9e54a9
5b08b2c
cf4c836
fb9cb76
35b71ac
76330ea
187b602
a835860
de796f3
6ba518b
bf14101
057dbac
e5654c3
ebf2c64
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
import testMd from "../../content/markdown-test.md"; | ||
import { renderWithRouter } from "../../utils/CustomRenderUtils"; | ||
|
||
import { MarkdownDirectory } from "./MarkdownDirectory"; | ||
import DirectoryAsPage from "./DirectoryAsPage"; | ||
|
||
describe("DirectoryAsPage", () => { | ||
const testDir = new MarkdownDirectory("Test Dir", "test-dir", [testMd]); | ||
test("Renders without error", () => { | ||
renderWithRouter(<DirectoryAsPage directory={testDir} />); | ||
}); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
/* Renders all files on a page for a directory */ | ||
import { MarkdownDirectory } from "./MarkdownDirectory"; | ||
import { MarkdownContent } from "./MarkdownContent"; | ||
|
||
const DirectoryAsPage = ({ directory }: { directory: MarkdownDirectory }) => { | ||
return ( | ||
<div> | ||
{directory.files.map((file, idx) => ( | ||
/* Because file != typeof string but this is what our spike showed us works. */ | ||
// @ts-ignore | ||
<MarkdownContent key={idx} markdownUrl={file} /> | ||
))} | ||
</div> | ||
); | ||
}; | ||
|
||
export default DirectoryAsPage; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
import { screen } from "@testing-library/react"; | ||
|
||
import { renderWithRouter } from "../../utils/CustomRenderUtils"; | ||
|
||
import GeneratedSideNav from "./GeneratedSideNav"; | ||
import { MarkdownDirectory } from "./MarkdownDirectory"; | ||
|
||
const TEST_DIRS = [ | ||
new MarkdownDirectory("Test Dir", "/test-dir", []), | ||
new MarkdownDirectory("Another Dir", "/another-dir", []), | ||
]; | ||
|
||
test("GeneratedSideNav", () => { | ||
renderWithRouter(<GeneratedSideNav directories={TEST_DIRS} />); | ||
expect(screen.getByText("Test Dir")).toBeInTheDocument(); | ||
expect(screen.getByText("Test Dir")).toHaveAttribute("href", "/test-dir"); | ||
expect(screen.getByText("Another Dir")).toBeInTheDocument(); | ||
expect(screen.getByText("Another Dir")).toHaveAttribute( | ||
"href", | ||
"/another-dir" | ||
); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
import { NavLink } from "react-router-dom"; | ||
import { SideNav } from "@trussworks/react-uswds"; | ||
|
||
import { MarkdownDirectory } from "./MarkdownDirectory"; | ||
|
||
export const GeneratedSideNav = ({ | ||
directories, | ||
}: { | ||
directories: MarkdownDirectory[]; | ||
}) => { | ||
const navItems = directories.map((dir) => ( | ||
<NavLink | ||
to={dir.slug} | ||
activeClassName="usa-current" | ||
className="usa-nav__link" | ||
> | ||
{dir.title} | ||
</NavLink> | ||
)); | ||
return <SideNav items={navItems} />; | ||
}; | ||
|
||
export default GeneratedSideNav; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
import React, { useEffect, useState } from "react"; | ||
import ReactMarkdown, { Options } from "react-markdown"; | ||
import rehypeSlug from "rehype-slug"; | ||
import remarkGfm from "remark-gfm"; | ||
import remarkToc from "remark-toc"; | ||
|
||
const baseOptions: Partial<Options> = { | ||
remarkPlugins: [ | ||
// Use GitHub-flavored markdown | ||
remarkGfm, | ||
// Generate a table of contents | ||
[remarkToc, { tight: true }], | ||
], | ||
rehypePlugins: [ | ||
// Add ids to headings so the table of contents can link to each section | ||
rehypeSlug, | ||
], | ||
}; | ||
|
||
type MarkdownContentProps = { | ||
// Relative URL of the webpack-bundled markdown file. This value can be determined by importing | ||
// the file as long as webpack is not configured to load the contents of the file, which is the | ||
// state that our version of Create React App is in. | ||
markdownUrl: string; | ||
}; | ||
|
||
export const MarkdownContent: React.FC<MarkdownContentProps> = ({ | ||
markdownUrl, | ||
}) => { | ||
const [markdownContent, setMarkdownContent] = useState(""); | ||
|
||
// Fetch the contents of the markdown file. | ||
// See: https://stackoverflow.com/questions/65395125/how-to-load-an-md-file-on-build-when-using-create-react-app-and-typescript | ||
useEffect(() => { | ||
fetch(markdownUrl) | ||
.then((response) => response.text()) | ||
.then((text) => { | ||
setMarkdownContent(text); | ||
}); | ||
}, [markdownUrl]); | ||
|
||
return <ReactMarkdown {...baseOptions} children={markdownContent} />; | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
import { MarkdownDirectory } from "./MarkdownDirectory"; | ||
|
||
describe("MarkdownDirectory", () => { | ||
const testDir = new MarkdownDirectory("Test Dir", "/test-dir", []); | ||
|
||
test("renders with params", () => { | ||
expect(testDir.title).toEqual("Test Dir"); | ||
expect(testDir.slug).toEqual("/test-dir"); | ||
expect(testDir.files).toEqual([]); | ||
}); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
/* Used to instantiate a set of static pages, like BuiltForYouIndex | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this looks more like a utility class than a component. Maybe it belongs in a utils file somewhere? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm on a mission to refactor out the utils files. They tend to become a junk drawer for relatively similar functions a lot of times. That said, I do think it should be either wrapped into a component file or put elsewhere. I think this could easily be merged into the |
||
* or HowItWorks */ | ||
import * as module from "module"; | ||
|
||
export interface MarkdownPageProps { | ||
directories: MarkdownDirectory[]; | ||
} | ||
|
||
/* Used to create objects that hold pointers to markdown directories and the | ||
* info needed to query them. This is because we cannot access the filesystem | ||
* at runtime */ | ||
export class MarkdownDirectory { | ||
title: string; | ||
slug: string; | ||
files: module[]; | ||
|
||
constructor(title: string, slug: string, files: module[]) { | ||
this.title = title; | ||
this.slug = slug; | ||
this.files = files; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
import { match } from "react-router-dom"; | ||
import { screen } from "@testing-library/react"; | ||
|
||
import testMd from "../../content/markdown-test.md"; | ||
import { renderWithRouter } from "../../utils/CustomRenderUtils"; | ||
|
||
import { MarkdownDirectory } from "./MarkdownDirectory"; | ||
import StaticPageFromDirectories from "./StaticPageFromDirectories"; | ||
|
||
const testDirectories = [ | ||
new MarkdownDirectory("Test Dir", "test-dir", [testMd]), | ||
]; | ||
|
||
jest.mock("react-router-dom", () => ({ | ||
...jest.requireActual("react-router-dom"), | ||
useRouteMatch: () => ({ path: "/test" } as match<{ path: string }>), | ||
})); | ||
|
||
describe("StaticPageFromDirectories", () => { | ||
test("Renders without error", () => { | ||
renderWithRouter( | ||
<StaticPageFromDirectories directories={testDirectories} /> | ||
); | ||
}); | ||
test("Renders without side-nav", () => { | ||
renderWithRouter( | ||
<StaticPageFromDirectories directories={testDirectories} /> | ||
); | ||
const nav = screen.getByText("Test Dir"); | ||
expect(nav).toBeInTheDocument(); | ||
expect(nav).toHaveAttribute("href", "/test-dir"); | ||
}); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
import { Route, Switch, useRouteMatch } from "react-router-dom"; | ||
|
||
import { CODES, ErrorPage } from "../../pages/error/ErrorPage"; | ||
|
||
import { MarkdownDirectory } from "./MarkdownDirectory"; | ||
import GeneratedSideNav from "./GeneratedSideNav"; | ||
import DirectoryAsPage from "./DirectoryAsPage"; | ||
|
||
const StaticPageFromDirectories = ({ | ||
directories, | ||
}: { | ||
directories: MarkdownDirectory[]; | ||
}) => { | ||
const { path } = useRouteMatch(); | ||
|
||
return ( | ||
<section className="grid-container tablet:margin-top-6 margin-bottom-5"> | ||
<div className="grid-row grid-gap"> | ||
<section className="tablet:grid-col-4 margin-bottom-6"> | ||
<GeneratedSideNav directories={directories} /> | ||
</section> | ||
<section className="tablet:grid-col-8 usa-prose rs-documentation"> | ||
<Switch> | ||
{/* SubRouter for /built-for-you */} | ||
{directories.map((dir) => ( | ||
<Route | ||
key={`${dir.slug}-route`} | ||
path={`${path}/${dir.slug}`} | ||
render={() => ( | ||
<DirectoryAsPage | ||
key={`${dir.slug}-dir-as-page`} | ||
directory={dir} | ||
/> | ||
)} | ||
/> | ||
))} | ||
{/* Handles any undefined route */} | ||
<Route | ||
render={() => ( | ||
<ErrorPage code={CODES.NOT_FOUND_404} /> | ||
)} | ||
/> | ||
</Switch> | ||
</section> | ||
</div> | ||
</section> | ||
); | ||
}; | ||
|
||
export default StaticPageFromDirectories; |
This file was deleted.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Big ugly line of config to get jest and react-markdown to play nice
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ouch! wonder if there's any way to point to this thread for further explanation remarkjs/react-markdown#635 (comment)