Upload starter template
This commit is contained in:
68
pages/_app.js
Normal file
68
pages/_app.js
Normal file
@ -0,0 +1,68 @@
|
||||
import '@/css/tailwind.css'
|
||||
|
||||
import { MDXProvider } from '@mdx-js/react'
|
||||
import { ThemeProvider } from 'next-themes'
|
||||
import { DefaultSeo } from 'next-seo'
|
||||
import Head from 'next/head'
|
||||
|
||||
import SEO from '@/components/SEO'
|
||||
import LayoutWrapper from '@/components/LayoutWrapper'
|
||||
import MDXComponents from '@/components/MDXComponents'
|
||||
|
||||
export default function App({ Component, pageProps }) {
|
||||
return (
|
||||
<ThemeProvider attribute="class">
|
||||
<MDXProvider components={MDXComponents}>
|
||||
<div className="antialiased">
|
||||
<Head>
|
||||
<meta content="width=device-width, initial-scale=1" name="viewport" />
|
||||
</Head>
|
||||
<DefaultSeo {...SEO} />
|
||||
<Head>
|
||||
<link
|
||||
rel="apple-touch-icon-precomposed"
|
||||
sizes="57x57"
|
||||
href="apple-touch-icon-57x57.png"
|
||||
/>
|
||||
<link
|
||||
rel="apple-touch-icon-precomposed"
|
||||
sizes="114x114"
|
||||
href="apple-touch-icon-114x114.png"
|
||||
/>
|
||||
<link
|
||||
rel="apple-touch-icon-precomposed"
|
||||
sizes="72x72"
|
||||
href="apple-touch-icon-72x72.png"
|
||||
/>
|
||||
<link
|
||||
rel="apple-touch-icon-precomposed"
|
||||
sizes="144x144"
|
||||
href="apple-touch-icon-144x144.png"
|
||||
/>
|
||||
<link
|
||||
rel="apple-touch-icon-precomposed"
|
||||
sizes="120x120"
|
||||
href="apple-touch-icon-120x120.png"
|
||||
/>
|
||||
<link
|
||||
rel="apple-touch-icon-precomposed"
|
||||
sizes="152x152"
|
||||
href="apple-touch-icon-152x152.png"
|
||||
/>
|
||||
<link rel="icon" type="image/png" href="favicon-32x32.png" sizes="32x32" />
|
||||
<link rel="icon" type="image/png" href="favicon-16x16.png" sizes="16x16" />
|
||||
<link rel="manifest" href="/manifest.json" />
|
||||
<meta name="application-name" content=" " />
|
||||
<meta name="msapplication-TileColor" content="#FFFFFF" />
|
||||
<meta name="msapplication-TileImage" content="mstile-144x144.png" />
|
||||
<meta name="theme-color" content="#ffffff" />
|
||||
<link rel="alternate" type="application/rss+xml" href="/feed.xml" />
|
||||
</Head>
|
||||
<LayoutWrapper>
|
||||
<Component {...pageProps} />
|
||||
</LayoutWrapper>
|
||||
</div>
|
||||
</MDXProvider>
|
||||
</ThemeProvider>
|
||||
)
|
||||
}
|
52
pages/_document.js
Normal file
52
pages/_document.js
Normal file
@ -0,0 +1,52 @@
|
||||
import Document, { Html, Head, Main, NextScript } from 'next/document'
|
||||
import GoogleFonts from 'next-google-fonts'
|
||||
|
||||
class MyDocument extends Document {
|
||||
render() {
|
||||
return (
|
||||
<Html lang="en">
|
||||
<GoogleFonts href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&display=swap" />
|
||||
<Head>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css"
|
||||
integrity="sha384-AfEj0r4/OFrOo5t7NnNe46zW/tFgW6x/bCJG8FqQCEo3+Aro6EYUG4+cU+KJWu/X"
|
||||
crossOrigin="anonymous"
|
||||
/>
|
||||
<link href="/static/favicons/favicon.ico" rel="shortcut icon" />
|
||||
<link href="/static/favicons/site.webmanifest" rel="manifest" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com/" crossOrigin="" />
|
||||
<link
|
||||
href="/static/favicons/apple-touch-icon.png"
|
||||
rel="apple-touch-icon"
|
||||
sizes="180x180"
|
||||
/>
|
||||
<link
|
||||
href="/static/favicons/favicon-32x32.png"
|
||||
rel="icon"
|
||||
sizes="32x32"
|
||||
type="image/png"
|
||||
/>
|
||||
<link
|
||||
href="/static/favicons/favicon-16x16.png"
|
||||
rel="icon"
|
||||
sizes="16x16"
|
||||
type="image/png"
|
||||
/>
|
||||
<link color="#00aba9" href="/static/favicons/safari-pinned-tab.svg" rel="mask-icon" />
|
||||
<link rel="alternate" type="application/rss+xml" href="/index.xml" />
|
||||
<meta content="IE=edge" httpEquiv="X-UA-Compatible" />
|
||||
<meta content="#ffffff" name="theme-color" />
|
||||
<meta content="#00aba9" name="msapplication-TileColor" />
|
||||
<meta content="/static/favicons/browserconfig.xml" name="msapplication-config" />
|
||||
</Head>
|
||||
<body className="bg-white dark:bg-gray-900 text-black dark:text-white">
|
||||
<Main />
|
||||
<NextScript />
|
||||
</body>
|
||||
</Html>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default MyDocument
|
60
pages/about.js
Normal file
60
pages/about.js
Normal file
@ -0,0 +1,60 @@
|
||||
import { NextSeo } from 'next-seo'
|
||||
import siteMetadata from '@/data/siteMetadata'
|
||||
import SocialIcon from '@/components/social-icons'
|
||||
|
||||
export default function About() {
|
||||
return (
|
||||
<>
|
||||
<NextSeo
|
||||
title={`About - ${siteMetadata.author}`}
|
||||
canonical={`${siteMetadata.siteUrl}/about`}
|
||||
openGraph={{
|
||||
url: `${siteMetadata.siteUrl}/about`,
|
||||
title: `About - ${siteMetadata.author}`,
|
||||
}}
|
||||
/>
|
||||
<div className="divide-y">
|
||||
<div className="pt-6 pb-8 space-y-2 md:space-y-5">
|
||||
<h1 className="text-3xl leading-9 font-extrabold text-gray-900 dark:text-gray-100 tracking-tight sm:text-4xl sm:leading-10 md:text-6xl md:leading-14">
|
||||
About
|
||||
</h1>
|
||||
</div>
|
||||
<div className="space-y-2 xl:grid xl:grid-cols-3 xl:gap-x-8 xl:space-y-0 items-start">
|
||||
<div className="flex flex-col items-center space-x-2 pt-8">
|
||||
<img src={siteMetadata.image} alt="avatar" className="w-48 h-48 rounded-full" />
|
||||
<h3 className="text-2xl leading-8 font-bold tracking-tight pt-4 pb-2">
|
||||
{siteMetadata.author}
|
||||
</h3>
|
||||
<div className="text-gray-500 dark:text-gray-400">Professor of Atmospheric Science</div>
|
||||
<div className="text-gray-500 dark:text-gray-400">Stanford University</div>
|
||||
<div className="flex pt-6 space-x-3">
|
||||
<SocialIcon kind="mail" href={`mailto:${siteMetadata.email}`} />
|
||||
<SocialIcon kind="github" href={siteMetadata.github} />
|
||||
<SocialIcon kind="facebook" href={siteMetadata.facebook} />
|
||||
<SocialIcon kind="youtube" href={siteMetadata.youtube} />
|
||||
<SocialIcon kind="linkedin" href={siteMetadata.linkedin} />
|
||||
<SocialIcon kind="twitter" href={siteMetadata.twitter} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="prose dark:prose-dark max-w-none pt-8 pb-8 xl:col-span-2">
|
||||
<p>
|
||||
Tails Azimuth is a professor of atmospheric sciences at the Stanford AI Lab. His
|
||||
research interests includes complexity modelling of tailwinds, headwinds and
|
||||
crosswinds.
|
||||
</p>
|
||||
<p>
|
||||
He leads the clean energy group which develops 3D air pollution-climate models, writes
|
||||
differential equation solvers, and manufactures titanium plated air ballons. In his
|
||||
free time he bakes raspberry pi.
|
||||
</p>
|
||||
<p>
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed neque elit, tristique
|
||||
placerat feugiat ac, facilisis vitae arcu. Proin eget egestas augue. Praesent ut sem
|
||||
nec arcu pellentesque aliquet. Duis dapibus diam vel metus tempus vulputate.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
30
pages/blog.js
Normal file
30
pages/blog.js
Normal file
@ -0,0 +1,30 @@
|
||||
import { NextSeo } from 'next-seo'
|
||||
import { getAllFilesFrontMatter } from '@/lib/mdx'
|
||||
import siteMetadata from '@/data/siteMetadata'
|
||||
import ListLayout from '@/layouts/ListLayout'
|
||||
|
||||
export async function getStaticProps() {
|
||||
const posts = await getAllFilesFrontMatter('blog')
|
||||
|
||||
return { props: { posts } }
|
||||
}
|
||||
|
||||
export default function Blog({ posts }) {
|
||||
|
||||
return (
|
||||
<>
|
||||
<NextSeo
|
||||
title={`Blog - ${siteMetadata.name}`}
|
||||
description={siteMetadata.description}
|
||||
canonical={`${siteMetadata.siteUrl}/blog`}
|
||||
openGraph={{
|
||||
url: `${siteMetadata.siteUrl}/blog`,
|
||||
title: `Blog - ${siteMetadata.name}`,
|
||||
description: siteMetadata.description,
|
||||
}}
|
||||
/>
|
||||
|
||||
<ListLayout posts={posts} title="All Posts"/>
|
||||
</>
|
||||
)
|
||||
}
|
50
pages/blog/[slug].js
Normal file
50
pages/blog/[slug].js
Normal file
@ -0,0 +1,50 @@
|
||||
import fs from 'fs'
|
||||
import hydrate from 'next-mdx-remote/hydrate'
|
||||
import { getFiles, getFileBySlug, getAllFilesFrontMatter } from '@/lib/mdx'
|
||||
import PostLayout from '@/layouts/PostLayout'
|
||||
import MDXComponents from '@/components/MDXComponents'
|
||||
import generateRss from '@/lib/generate-rss'
|
||||
|
||||
export async function getStaticPaths() {
|
||||
const posts = await getFiles('blog')
|
||||
|
||||
return {
|
||||
paths: posts.map((p) => ({
|
||||
params: {
|
||||
slug: p.replace(/\.(mdx|md)/, ''),
|
||||
},
|
||||
})),
|
||||
fallback: false,
|
||||
}
|
||||
}
|
||||
|
||||
export async function getStaticProps({ params }) {
|
||||
const allPosts = await getAllFilesFrontMatter('blog')
|
||||
const postIndex = allPosts.findIndex((post) => post.slug === params.slug)
|
||||
const prev = allPosts[postIndex + 1] || null
|
||||
const next = allPosts[postIndex - 1] || null
|
||||
const post = await getFileBySlug('blog', params.slug)
|
||||
|
||||
// rss
|
||||
const rss = generateRss(allPosts)
|
||||
fs.writeFileSync('./public/index.xml', rss)
|
||||
|
||||
return { props: { post, prev, next } }
|
||||
}
|
||||
|
||||
export default function Blog({ post, prev, next }) {
|
||||
const { mdxSource, frontMatter } = post
|
||||
const content = hydrate(mdxSource, {
|
||||
components: MDXComponents,
|
||||
})
|
||||
|
||||
return (
|
||||
<>
|
||||
{frontMatter.draft !== true && (
|
||||
<PostLayout frontMatter={frontMatter} prev={prev} next={next}>
|
||||
{content}
|
||||
</PostLayout>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
90
pages/index.js
Normal file
90
pages/index.js
Normal file
@ -0,0 +1,90 @@
|
||||
import tinytime from 'tinytime'
|
||||
import Link from 'next/link'
|
||||
import { getAllFilesFrontMatter } from '@/lib/mdx'
|
||||
import siteMetadata from '@/data/siteMetadata'
|
||||
import Tag from '@/components/Tag'
|
||||
|
||||
const MAX_DISPLAY = 5
|
||||
const postDateTemplate = tinytime('{MMMM} {DD}, {YYYY}')
|
||||
|
||||
export async function getStaticProps() {
|
||||
const posts = await getAllFilesFrontMatter('blog')
|
||||
|
||||
return { props: { posts } }
|
||||
}
|
||||
|
||||
export default function Home({ posts }) {
|
||||
return (
|
||||
<div>
|
||||
<div className="divide-y divide-gray-200 dark:divide-gray-700">
|
||||
<div className="pt-6 pb-8 space-y-2 md:space-y-5">
|
||||
<h1 className="text-3xl leading-9 font-extrabold text-gray-900 dark:text-gray-100 tracking-tight sm:text-4xl sm:leading-10 md:text-6xl md:leading-14">
|
||||
Latest
|
||||
</h1>
|
||||
<p className="text-lg leading-7 text-gray-500 dark:text-gray-400">
|
||||
{siteMetadata.description}
|
||||
</p>
|
||||
</div>
|
||||
<ul className="divide-y divide-gray-200 dark:divide-gray-700">
|
||||
{!posts.length && 'No posts found.'}
|
||||
{posts.slice(0, MAX_DISPLAY).map((frontMatter) => {
|
||||
const { slug, date, title, summary, tags } = frontMatter
|
||||
return (
|
||||
<li key={slug} className="py-12">
|
||||
<article className="space-y-2 xl:grid xl:grid-cols-4 xl:space-y-0 xl:items-baseline">
|
||||
<dl>
|
||||
<dt className="sr-only">Published on</dt>
|
||||
<dd className="text-base leading-6 font-medium text-gray-500 dark:text-gray-400">
|
||||
<time dateTime={date}>{postDateTemplate.render(new Date(date))}</time>
|
||||
</dd>
|
||||
</dl>
|
||||
<div className="space-y-5 xl:col-span-3">
|
||||
<div className="space-y-6">
|
||||
<div>
|
||||
<h2 className="text-2xl leading-8 font-bold tracking-tight">
|
||||
<Link href={`/blog/${slug}`}>
|
||||
<a className="text-gray-900 dark:text-gray-100">{title}</a>
|
||||
</Link>
|
||||
</h2>
|
||||
<div className="space-x-2">
|
||||
{tags.map((tag) => (
|
||||
<Tag key={tag} text={tag} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div className="prose max-w-none text-gray-500 dark:text-gray-400">
|
||||
{summary}
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-base leading-6 font-medium">
|
||||
<Link href={`/blog/${slug}`}>
|
||||
<a
|
||||
className="text-blue-500 hover:text-blue-600 dark:hover:text-blue-400"
|
||||
aria-label={`Read "${title}"`}
|
||||
>
|
||||
Read more →
|
||||
</a>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
</li>
|
||||
)
|
||||
})}
|
||||
</ul>
|
||||
</div>
|
||||
{posts.length > MAX_DISPLAY && (
|
||||
<div className="flex justify-end text-base leading-6 font-medium">
|
||||
<Link href="/blog">
|
||||
<a
|
||||
className="text-blue-500 hover:text-blue-600 dark:hover:text-blue-400"
|
||||
aria-label="all posts"
|
||||
>
|
||||
All Posts →
|
||||
</a>
|
||||
</Link>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
48
pages/tags.js
Normal file
48
pages/tags.js
Normal file
@ -0,0 +1,48 @@
|
||||
import Link from 'next/link'
|
||||
import kebabCase from 'just-kebab-case'
|
||||
import { NextSeo } from 'next-seo'
|
||||
import siteMetadata from '@/data/siteMetadata'
|
||||
import { getAllTags } from '@/lib/tags'
|
||||
import Tag from '@/components/Tag'
|
||||
|
||||
export async function getStaticProps() {
|
||||
const tags = await getAllTags('blog')
|
||||
|
||||
return { props: { tags } }
|
||||
}
|
||||
|
||||
export default function Tags({ tags }) {
|
||||
const sortedTags = Object.keys(tags).sort((a, b) => tags[b] - tags[a])
|
||||
return (
|
||||
<>
|
||||
<NextSeo
|
||||
title={`Tags - ${siteMetadata.title}`}
|
||||
canonical={`${siteMetadata.siteUrl}/tags`}
|
||||
openGraph={{
|
||||
url: `${siteMetadata.siteUrl}/tags`,
|
||||
title: `Tags - ${siteMetadata.title}`,
|
||||
}}
|
||||
/>
|
||||
<div className="flex items-start justify-start flex-col divide-y divide-gray-200 dark:divide-gray-700 md:justify-center md:items-center md:divide-y-0 md:flex-row md:space-x-6 md:mt-24">
|
||||
<div className="pt-6 pb-8 space-x-2 md:space-y-5">
|
||||
<h1 className="text-3xl leading-9 font-extrabold text-gray-900 dark:text-gray-100 tracking-tight sm:text-4xl sm:leading-10 md:text-6xl md:leading-14 md:border-r-2 md:px-6">
|
||||
Tags
|
||||
</h1>
|
||||
</div>
|
||||
<div className="max-w-lg flex flex-wrap">
|
||||
{Object.keys(tags).length === 0 && 'No tags found.'}
|
||||
{sortedTags.map((t) => {
|
||||
return (
|
||||
<div key={t} className="m-2">
|
||||
<Tag text={t} />
|
||||
<Link href={`/tags/${kebabCase(t)}`}>
|
||||
<a className="uppercase font-semibold text-sm mx-1 text-gray-600 dark:text-gray-300">{` (${tags[t]})`}</a>
|
||||
</Link>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
47
pages/tags/[tag].js
Normal file
47
pages/tags/[tag].js
Normal file
@ -0,0 +1,47 @@
|
||||
import { NextSeo } from 'next-seo'
|
||||
import { getAllFilesFrontMatter } from '@/lib/mdx'
|
||||
import { getAllTags } from '@/lib/tags'
|
||||
import siteMetadata from '@/data/siteMetadata'
|
||||
import ListLayout from '@/layouts/ListLayout'
|
||||
import kebabCase from 'just-kebab-case'
|
||||
|
||||
export async function getStaticPaths() {
|
||||
const tags = await getAllTags('blog')
|
||||
|
||||
return {
|
||||
paths: Object.keys(tags).map((tag) => ({
|
||||
params: {
|
||||
tag,
|
||||
},
|
||||
})),
|
||||
fallback: false,
|
||||
}
|
||||
}
|
||||
|
||||
export async function getStaticProps({ params }) {
|
||||
const allPosts = await getAllFilesFrontMatter('blog')
|
||||
const filteredPosts = allPosts.filter(
|
||||
(post) => post.draft !== true && post.tags.map((t) => kebabCase(t)).includes(params.tag)
|
||||
)
|
||||
|
||||
return { props: { posts: filteredPosts, tag: params.tag } }
|
||||
}
|
||||
|
||||
export default function Blog({ posts, tag }) {
|
||||
// Capitalize first letter and convert space to dash
|
||||
const title = tag[0].toUpperCase() + tag.split(' ').join('-').slice(1)
|
||||
return (
|
||||
<>
|
||||
<NextSeo
|
||||
title={`${tag} - ${siteMetadata.title}`}
|
||||
description={siteMetadata.description}
|
||||
canonical={`${siteMetadata.siteUrl}/tags/${tag}`}
|
||||
openGraph={{
|
||||
url: `${siteMetadata.siteUrl}/tags/${tag}`,
|
||||
title: `${tag} - ${siteMetadata.title}`,
|
||||
}}
|
||||
/>
|
||||
<ListLayout posts={posts} title={title} />
|
||||
</>
|
||||
)
|
||||
}
|
Reference in New Issue
Block a user