Skip to content

Commit

Permalink
[site,#4][l]: process all markdown pages from root directory down.
Browse files Browse the repository at this point in the history
* Markdown / MDX support
* customize directories to ignore in searching for markdown (e.g. node_modules, site, .next)
* Navbar and Layout component
  • Loading branch information
rufuspollock committed Aug 17, 2021
1 parent b3c890b commit 5081563
Show file tree
Hide file tree
Showing 10 changed files with 1,734 additions and 12 deletions.
16 changes: 16 additions & 0 deletions site/components/CustomLink.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import Link from 'next/link'

export default function CustomLink({ as, href, ...otherProps }) {
return (
<>
<Link as={as} href={href}>
<a {...otherProps} />
</Link>
<style jsx>{`
a {
color: tomato;
}
`}</style>
</>
)
}
32 changes: 32 additions & 0 deletions site/components/Layout.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import Link from 'next/link'
import Head from 'next/head'

import Nav from '../components/Nav'

export default function Layout({ children, title = 'Home' }) {
return (
<>
<Head>
<title>Climatology 🌍🔥 - {title}</title>
<link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>🔥</text></svg>" />
<meta charSet="utf-8" />
<meta name="viewport" content="initial-scale=1.0, width=device-width" />
</Head>
<Nav />
<div className="prose mx-auto p-6">
{children}
</div>
<footer className="flex items-center justify-center w-full h-24 border-t">
<a
className="flex items-center justify-center"
href="https://vercel.com?utm_source=create-next-app&utm_medium=default-template&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
A Project of
<img src="/life-itself-logo.svg" alt="Life Itself Logo" className="h-12 block" />
</a>
</footer>
</>
)
}
84 changes: 84 additions & 0 deletions site/components/Nav.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import { Fragment } from 'react'
import { Disclosure, Menu, Transition } from '@headlessui/react'
import { BellIcon, MenuIcon, XIcon } from '@heroicons/react/outline'

import Link from 'next/link'

const navigation = [
]

function classNames(...classes) {
return classes.filter(Boolean).join(' ')
}

export default function Nav() {
return (
<Disclosure as="nav" className="bg-gray-800">
{({ open }) => (
<>
<div className="max-w-7xl mx-auto px-2 sm:px-6 lg:px-8">
<div className="relative flex items-center justify-between h-16">
<div className="absolute inset-y-0 left-0 flex items-center sm:hidden">
{/* Mobile menu button*/}
<Disclosure.Button className="inline-flex items-center justify-center p-2 rounded-md text-gray-400 hover:text-white hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-inset focus:ring-white">
<span className="sr-only">Open main menu</span>
{open ? (
<XIcon className="block h-6 w-6" aria-hidden="true" />
) : (
<MenuIcon className="block h-6 w-6" aria-hidden="true" />
)}
</Disclosure.Button>
</div>
<div className="flex-1 flex items-center justify-center sm:items-stretch sm:justify-start">
<div className="flex-shrink-0 flex items-center">
<Link href="/">
<a className="text-white">
Climatology 🌍🔥
</a>
</Link>
</div>
<div className="hidden sm:block sm:ml-6">
<div className="flex space-x-4">
{navigation.map((item) => (
<Link href={item.href}>
<a
key={item.name}
href={item.href}
className={classNames(
item.current ? 'bg-gray-900 text-white' : 'text-gray-300 hover:bg-gray-700 hover:text-white',
'px-3 py-2 rounded-md text-sm font-medium'
)}
aria-current={item.current ? 'page' : undefined}
>
{item.name}
</a>
</Link>
))}
</div>
</div>
</div>
</div>
</div>

<Disclosure.Panel className="sm:hidden">
<div className="px-2 pt-2 pb-3 space-y-1">
{navigation.map((item) => (
<a
key={item.name}
href={item.href}
className={classNames(
item.current ? 'bg-gray-900 text-white' : 'text-gray-300 hover:bg-gray-700 hover:text-white',
'block px-3 py-2 rounded-md text-base font-medium'
)}
aria-current={item.current ? 'page' : undefined}
>
{item.name}
</a>
))}
</div>
</Disclosure.Panel>
</>
)}
</Disclosure>
)
}
34 changes: 34 additions & 0 deletions site/components/Page.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import Layout from '../components/Layout'

import { MDXRemote } from 'next-mdx-remote'
import dynamic from 'next/dynamic'
import Head from 'next/head'
import Link from 'next/link'

import CustomLink from '../components/CustomLink'

const components = {
a: CustomLink,
Head,
}

export default function Page({ children, source, frontMatter }) {
return (
<Layout title={frontMatter.title}>
<header>
<div className="mb-6">
<h1>{frontMatter.title}</h1>
{frontMatter.author && (
<div className="-mt-6"><p className="opacity-60 pl-1">{frontMatter.author}</p></div>
)}
{frontMatter.description && (
<p className="description">{frontMatter.description}</p>
)}
</div>
</header>
<main>
<MDXRemote {...source} components={components} />
</main>
</Layout>
)
}
33 changes: 33 additions & 0 deletions site/lib/markdown.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import matter from 'gray-matter'
import toc from 'remark-toc'
import slug from 'remark-slug'
import gfm from 'remark-gfm'
import footnotes from 'remark-footnotes'

import { serialize } from 'next-mdx-remote/serialize'

/**
* Parse a markdown or MDX file to an MDX source form + front matter data
*
* @source: the contents of a markdown or mdx file
* @returns: { mdxSource: mdxSource, frontMatter: ...}
*/
const parse = async function(source) {
const { content, data } = matter(source)

const mdxSource = await serialize(content, {
// Optionally pass remark/rehype plugins
mdxOptions: {
remarkPlugins: [gfm, toc, slug, footnotes],
rehypePlugins: [],
},
scope: data,
})

return {
mdxSource: mdxSource,
frontMatter: data
}
}

export default parse
11 changes: 10 additions & 1 deletion site/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,18 @@
"start": "next start"
},
"dependencies": {
"@headlessui/react": "^1.4.0",
"@heroicons/react": "^1.0.3",
"@tailwindcss/typography": "^0.4.1",
"gray-matter": "^4.0.3",
"next": "latest",
"next-mdx-remote": "^3.0.4",
"react": "^17.0.2",
"react-dom": "^17.0.2"
"react-dom": "^17.0.2",
"remark-footnotes": "^4.0.0",
"remark-gfm": "^2.0.0",
"remark-slug": "^7.0.0",
"remark-toc": "^8.0.0"
},
"devDependencies": {
"autoprefixer": "^10.2.6",
Expand Down
71 changes: 71 additions & 0 deletions site/pages/[...slug].js
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import fs from 'fs'
import path from 'path'

import parse from '../lib/markdown.js'

import Page from '../components/Page'


export default function PostPage({ source, frontMatter }) {
return (
<Page source={source} frontMatter={frontMatter} />
)
}

export const getStaticProps = async ({ params }) => {
const mdxPath = path.join(POSTS_PATH, `${params.slug.join('/')}.mdx`)
const postFilePath = fs.existsSync(mdxPath) ? mdxPath : mdxPath.slice(0, -1)
const source = fs.readFileSync(postFilePath)

const { mdxSource, frontMatter } = await parse(source)

return {
props: {
source: mdxSource,
frontMatter: frontMatter,
},
}
}

export const getStaticPaths = async () => {
const walkSync = (dir, filelist = []) => {
fs.readdirSync(dir).forEach(file => {
const tpath = fs.statSync(path.join(dir, file))
if (tpath.isDirectory()) {
if (! DIR_EXCLUDES.includes(file) ) {
filelist = walkSync(path.join(dir, file), filelist)
}
} else {
filelist = filelist.concat(path.join(dir, file))
}
})
return filelist
}

// postFilePaths is the list of all mdx files inside the POSTS_PATH directory
var filePaths = walkSync(POSTS_PATH)
.map((file) => { return file.slice(POSTS_PATH.length) })
// Only include md(x) files
.filter((path) => /\.mdx?$/.test(path))

var filePaths = filePaths
// Remove file extensions for page paths
.map((path) => path.replace(/\.mdx?$/, ''))

// Map the path into the static paths object required by Next.js
const paths = filePaths.map((slug) => {
// /demo => [demo]
const parts = slug.split('/')
return { params: { slug: parts } }
})

return {
paths,
fallback: false,
}
}

const POSTS_PATH = path.join(process.cwd(), '../')

const DIR_EXCLUDES = ['node_modules', 'site', '.next', 'sandbox' ]

7 changes: 7 additions & 0 deletions site/public/life-itself-logo.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 3 additions & 1 deletion site/tailwind.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,7 @@ module.exports = {
variants: {
extend: {},
},
plugins: [],
plugins: [
require('@tailwindcss/typography'),
],
}
Loading

0 comments on commit 5081563

Please sign in to comment.