mirror of
https://github.com/jakejarvis/jarv.is.git
synced 2025-07-03 16:46:39 -04:00
consolidate mdx file parsing
This commit is contained in:
@ -1,6 +1,5 @@
|
|||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { format, parseISO } from "date-fns";
|
import { format } from "date-fns";
|
||||||
import groupBy from "lodash.groupby";
|
|
||||||
|
|
||||||
import styles from "./List.module.scss";
|
import styles from "./List.module.scss";
|
||||||
|
|
||||||
@ -10,18 +9,17 @@ type NoteProps = {
|
|||||||
slug: string;
|
slug: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
const List = ({ notes }) => {
|
const List = ({ notesByYear }) => {
|
||||||
const notesByYear = groupBy(notes, "year");
|
|
||||||
const sections = [];
|
const sections = [];
|
||||||
|
|
||||||
Object.entries(notesByYear).forEach(([year, yearNotes]: [string, NoteProps[]]) => {
|
Object.entries(notesByYear).forEach(([year, notes]: [string, NoteProps[]]) => {
|
||||||
sections.push(
|
sections.push(
|
||||||
<section key={year} className={styles.section}>
|
<section key={year} className={styles.section}>
|
||||||
<h2 className={styles.year}>{year}</h2>
|
<h2 className={styles.year}>{year}</h2>
|
||||||
<ul className={styles.list}>
|
<ul className={styles.list}>
|
||||||
{yearNotes.map((note) => (
|
{notes.map((note) => (
|
||||||
<li key={note.slug} className={styles.row}>
|
<li key={note.slug} className={styles.row}>
|
||||||
<span className={styles.date}>{format(parseISO(note.date), "MMM d")}</span>
|
<span className={styles.date}>{format(new Date(note.date), "MMM d")}</span>
|
||||||
<span>
|
<span>
|
||||||
<Link href={`/notes/${note.slug}/`} prefetch={false}>
|
<Link href={`/notes/${note.slug}/`} prefetch={false}>
|
||||||
<a>{note.title}</a>
|
<a>{note.title}</a>
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { format, parseISO } from "date-fns";
|
import { format } from "date-fns";
|
||||||
import Hits from "../hits/Hits";
|
import Hits from "../hits/Hits";
|
||||||
import { DateIcon, TagIcon, EditIcon, ViewsIcon } from "../icons";
|
import { DateIcon, TagIcon, EditIcon, ViewsIcon } from "../icons";
|
||||||
import * as config from "../../lib/config";
|
import * as config from "../../lib/config";
|
||||||
@ -20,8 +20,8 @@ const Meta = ({ title, date, slug, tags = [] }: Props) => (
|
|||||||
<span>
|
<span>
|
||||||
<DateIcon className={`icon ${styles.icon}`} />
|
<DateIcon className={`icon ${styles.icon}`} />
|
||||||
</span>
|
</span>
|
||||||
<span title={format(parseISO(date), "PPppp")}>
|
<span title={format(new Date(date), "PPppp")}>
|
||||||
<Link href={`/notes/${slug}/`}>{format(parseISO(date), "MMMM d, yyyy")}</Link>
|
<Link href={`/notes/${slug}/`}>{format(new Date(date), "MMMM d, yyyy")}</Link>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
{tags.length > 0 && (
|
{tags.length > 0 && (
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { intlFormat, formatDistanceToNowStrict, parseISO } from "date-fns";
|
import { intlFormat, formatDistanceToNowStrict } from "date-fns";
|
||||||
import { StarOcticon, ForkOcticon } from "../icons/octicons";
|
import { StarOcticon, ForkOcticon } from "../icons/octicons";
|
||||||
|
|
||||||
import styles from "./RepoCard.module.scss";
|
import styles from "./RepoCard.module.scss";
|
||||||
@ -69,7 +69,7 @@ const RepoCard = ({ name, url, description, language, stars, forks, updatedAt }:
|
|||||||
<div
|
<div
|
||||||
className={styles.meta_item}
|
className={styles.meta_item}
|
||||||
title={intlFormat(
|
title={intlFormat(
|
||||||
parseISO(updatedAt),
|
new Date(updatedAt),
|
||||||
{
|
{
|
||||||
year: "numeric",
|
year: "numeric",
|
||||||
month: "short",
|
month: "short",
|
||||||
@ -83,7 +83,7 @@ const RepoCard = ({ name, url, description, language, stars, forks, updatedAt }:
|
|||||||
}
|
}
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<span>Updated {formatDistanceToNowStrict(parseISO(updatedAt), { addSuffix: true })}</span>
|
<span>Updated {formatDistanceToNowStrict(new Date(updatedAt), { addSuffix: true })}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,30 +1,33 @@
|
|||||||
import fs from "fs";
|
import fs from "fs";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import matter from "gray-matter";
|
import matter from "gray-matter";
|
||||||
import { format, parseISO } from "date-fns";
|
import { NOTES_DIR, baseUrl } from "./config";
|
||||||
import * as config from "./config";
|
|
||||||
|
|
||||||
export const getNoteData = (file: string) => {
|
export const getNoteData = (slug: string) => {
|
||||||
const slug = file.replace(/\.mdx$/, "");
|
const fullPath = path.join(process.cwd(), NOTES_DIR, `${slug}.mdx`);
|
||||||
const fullPath = path.join(process.cwd(), config.NOTES_DIR, `${slug}.mdx`);
|
const rawContent = fs.readFileSync(fullPath, "utf8");
|
||||||
const contents = fs.readFileSync(fullPath, "utf8");
|
const { data, content } = matter(rawContent);
|
||||||
const { data } = matter(contents);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...data,
|
frontMatter: {
|
||||||
slug,
|
...data,
|
||||||
permalink: `${config.baseUrl}/notes/${slug}/`,
|
slug,
|
||||||
date: parseISO(data.date).toISOString(), // validate/normalize the date string provided from front matter
|
permalink: `${baseUrl}/notes/${slug}/`,
|
||||||
year: parseInt(format(parseISO(data.date), "yyyy")), // parse years here so it's easier to group them on list page
|
date: new Date(data.date).toISOString(), // validate/normalize the date string provided from front matter
|
||||||
|
},
|
||||||
|
content,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
// all .mdx files in NOTES_DIR
|
// all .mdx files in NOTES_DIR
|
||||||
export const getNoteFiles = () =>
|
export const getNoteSlugs = () =>
|
||||||
fs.readdirSync(path.join(process.cwd(), config.NOTES_DIR)).filter((notePath) => /\.mdx$/.test(notePath));
|
fs
|
||||||
|
.readdirSync(path.join(process.cwd(), NOTES_DIR))
|
||||||
|
.filter((file) => /\.mdx$/.test(file))
|
||||||
|
.map((noteFile) => noteFile.replace(/\.mdx$/, ""));
|
||||||
|
|
||||||
export const getAllNotes = () =>
|
export const getAllNotes = () =>
|
||||||
getNoteFiles()
|
getNoteSlugs()
|
||||||
.map((file) => getNoteData(file))
|
.map((slug) => getNoteData(slug).frontMatter)
|
||||||
// sort notes by date in descending order
|
// sort notes by date in descending order
|
||||||
.sort((note1: any, note2: any) => (note1.date > note2.date ? -1 : 1));
|
.sort((note1: any, note2: any) => (note1.date > note2.date ? -1 : 1));
|
||||||
|
@ -43,7 +43,6 @@
|
|||||||
"hex-rgb": "^5.0.0",
|
"hex-rgb": "^5.0.0",
|
||||||
"highlight.js": "^11.3.1",
|
"highlight.js": "^11.3.1",
|
||||||
"is-absolute-url": "^4.0.1",
|
"is-absolute-url": "^4.0.1",
|
||||||
"lodash.groupby": "^4.6.0",
|
|
||||||
"modern-normalize": "github:sindresorhus/modern-normalize#1fc6b5a86676b7ac8abc62d04d6080f92debc70f",
|
"modern-normalize": "github:sindresorhus/modern-normalize#1fc6b5a86676b7ac8abc62d04d6080f92debc70f",
|
||||||
"next": "v12.0.8-canary.14",
|
"next": "v12.0.8-canary.14",
|
||||||
"next-mdx-remote": "^3.0.8",
|
"next-mdx-remote": "^3.0.8",
|
||||||
|
@ -14,7 +14,7 @@ type ColorLinkProps = {
|
|||||||
external?: boolean;
|
external?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
const ColorLink = ({ children, href, lightColor, darkColor, title, external = false }: ColorLinkProps) => {
|
const ColorLink = ({ href, title, lightColor, darkColor, external = false, children }: ColorLinkProps) => {
|
||||||
external = external || isAbsoluteUrl(href);
|
external = external || isAbsoluteUrl(href);
|
||||||
|
|
||||||
// spits out an alpha color in rgb() that's compatible with linear-gradient()
|
// spits out an alpha color in rgb() that's compatible with linear-gradient()
|
||||||
|
@ -1,7 +1,3 @@
|
|||||||
import fs from "fs";
|
|
||||||
import path from "path";
|
|
||||||
import matter from "gray-matter";
|
|
||||||
import { parseISO } from "date-fns";
|
|
||||||
import { MDXRemote } from "next-mdx-remote";
|
import { MDXRemote } from "next-mdx-remote";
|
||||||
import { serialize } from "next-mdx-remote/serialize";
|
import { serialize } from "next-mdx-remote/serialize";
|
||||||
import { NextSeo, ArticleJsonLd } from "next-seo";
|
import { NextSeo, ArticleJsonLd } from "next-seo";
|
||||||
@ -10,7 +6,7 @@ import Container from "../../components/Container";
|
|||||||
import Content from "../../components/Content";
|
import Content from "../../components/Content";
|
||||||
import Meta from "../../components/notes/Meta";
|
import Meta from "../../components/notes/Meta";
|
||||||
import mdxComponents from "../../components/mdxComponents";
|
import mdxComponents from "../../components/mdxComponents";
|
||||||
import { getNoteFiles } from "../../lib/parse-notes";
|
import { getNoteData, getNoteSlugs } from "../../lib/parse-notes";
|
||||||
import * as config from "../../lib/config";
|
import * as config from "../../lib/config";
|
||||||
import type { GetStaticProps, GetStaticPaths } from "next";
|
import type { GetStaticProps, GetStaticPaths } from "next";
|
||||||
|
|
||||||
@ -72,12 +68,9 @@ const Note = ({ frontMatter, source }) => (
|
|||||||
);
|
);
|
||||||
|
|
||||||
export const getStaticProps: GetStaticProps = async ({ params }) => {
|
export const getStaticProps: GetStaticProps = async ({ params }) => {
|
||||||
const filePath = path.join(process.cwd(), config.NOTES_DIR, `${params.slug}.mdx`);
|
const { frontMatter, content } = getNoteData(params.slug as string);
|
||||||
const rawSource = fs.readFileSync(filePath);
|
|
||||||
const { data, content } = matter(rawSource);
|
|
||||||
|
|
||||||
const mdxSource = await serialize(content, {
|
const source = await serialize(content, {
|
||||||
scope: data,
|
|
||||||
mdxOptions: {
|
mdxOptions: {
|
||||||
// remarkPlugins: [],
|
// remarkPlugins: [],
|
||||||
rehypePlugins: [
|
rehypePlugins: [
|
||||||
@ -94,23 +87,14 @@ export const getStaticProps: GetStaticProps = async ({ params }) => {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
props: {
|
props: {
|
||||||
frontMatter: {
|
frontMatter,
|
||||||
...data,
|
source,
|
||||||
slug: params.slug,
|
|
||||||
permalink: `${config.baseUrl}/notes/${params.slug}/`,
|
|
||||||
date: parseISO(data.date).toISOString(), // validate/normalize the date string provided from front matter
|
|
||||||
},
|
|
||||||
source: mdxSource,
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getStaticPaths: GetStaticPaths = async () => {
|
export const getStaticPaths: GetStaticPaths = async () => {
|
||||||
const paths = getNoteFiles()
|
const paths = getNoteSlugs().map((slug) => ({ params: { slug } }));
|
||||||
// Remove file extensions for page paths
|
|
||||||
.map((notePath) => notePath.replace(/\.mdx?$/, ""))
|
|
||||||
// Map the path into the static paths object required by Next.js
|
|
||||||
.map((slug) => ({ params: { slug } }));
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
paths,
|
paths,
|
||||||
|
@ -1,23 +1,29 @@
|
|||||||
|
import { format } from "date-fns";
|
||||||
import Layout from "../../components/Layout";
|
import Layout from "../../components/Layout";
|
||||||
import Container from "../../components/Container";
|
import Container from "../../components/Container";
|
||||||
import List from "../../components/notes/List";
|
import List from "../../components/notes/List";
|
||||||
import { getAllNotes } from "../../lib/parse-notes";
|
import { getAllNotes } from "../../lib/parse-notes";
|
||||||
import type { GetStaticProps } from "next";
|
import type { GetStaticProps } from "next";
|
||||||
|
|
||||||
const Notes = ({ notes }) => (
|
const Notes = ({ notesByYear }) => (
|
||||||
<Layout>
|
<Layout>
|
||||||
<Container title="Notes" description="Recent posts by Jake Jarvis.">
|
<Container title="Notes" description="Recent posts by Jake Jarvis.">
|
||||||
<List notes={notes} />
|
<List notesByYear={notesByYear} />
|
||||||
</Container>
|
</Container>
|
||||||
</Layout>
|
</Layout>
|
||||||
);
|
);
|
||||||
|
|
||||||
export const getStaticProps: GetStaticProps = async () => {
|
export const getStaticProps: GetStaticProps = async () => {
|
||||||
const notes = getAllNotes();
|
// parse the year of each note and group them together
|
||||||
|
const notesByYear = {};
|
||||||
|
getAllNotes().map((note) => {
|
||||||
|
const year = parseInt(format(new Date(note.date), "yyyy"));
|
||||||
|
(notesByYear[year] || (notesByYear[year] = [])).push(note);
|
||||||
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
props: {
|
props: {
|
||||||
notes,
|
notesByYear,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
23
yarn.lock
23
yarn.lock
@ -3807,11 +3807,6 @@ lodash.debounce@^4.0.8:
|
|||||||
resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af"
|
resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af"
|
||||||
integrity sha1-gteb/zCmfEAF/9XiUVMArZyk168=
|
integrity sha1-gteb/zCmfEAF/9XiUVMArZyk168=
|
||||||
|
|
||||||
lodash.groupby@^4.6.0:
|
|
||||||
version "4.6.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/lodash.groupby/-/lodash.groupby-4.6.0.tgz#0b08a1dcf68397c397855c3239783832df7403d1"
|
|
||||||
integrity sha1-Cwih3PaDl8OXhVwyOXg4Mt90A9E=
|
|
||||||
|
|
||||||
lodash.merge@^4.6.2:
|
lodash.merge@^4.6.2:
|
||||||
version "4.6.2"
|
version "4.6.2"
|
||||||
resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a"
|
resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a"
|
||||||
@ -4419,7 +4414,7 @@ path-key@^3.0.0, path-key@^3.1.0:
|
|||||||
resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375"
|
resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375"
|
||||||
integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==
|
integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==
|
||||||
|
|
||||||
path-parse@^1.0.6:
|
path-parse@^1.0.6, path-parse@^1.0.7:
|
||||||
version "1.0.7"
|
version "1.0.7"
|
||||||
resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735"
|
resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735"
|
||||||
integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==
|
integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==
|
||||||
@ -4871,12 +4866,13 @@ resolve-from@^5.0.0:
|
|||||||
integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==
|
integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==
|
||||||
|
|
||||||
resolve@^1.10.0, resolve@^1.14.2, resolve@^1.20.0, resolve@^1.3.2:
|
resolve@^1.10.0, resolve@^1.14.2, resolve@^1.20.0, resolve@^1.3.2:
|
||||||
version "1.20.0"
|
version "1.21.0"
|
||||||
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.20.0.tgz#629a013fb3f70755d6f0b7935cc1c2c5378b1975"
|
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.21.0.tgz#b51adc97f3472e6a5cf4444d34bc9d6b9037591f"
|
||||||
integrity sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==
|
integrity sha512-3wCbTpk5WJlyE4mSOtDLhqQmGFi0/TD9VPwmiolnk8U0wRgMEktqCXd3vy5buTO3tljvalNvKrjHEfrd2WpEKA==
|
||||||
dependencies:
|
dependencies:
|
||||||
is-core-module "^2.2.0"
|
is-core-module "^2.8.0"
|
||||||
path-parse "^1.0.6"
|
path-parse "^1.0.7"
|
||||||
|
supports-preserve-symlinks-flag "^1.0.0"
|
||||||
|
|
||||||
resolve@^2.0.0-next.3:
|
resolve@^2.0.0-next.3:
|
||||||
version "2.0.0-next.3"
|
version "2.0.0-next.3"
|
||||||
@ -5462,6 +5458,11 @@ supports-color@^9.2.1:
|
|||||||
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-9.2.1.tgz#599dc9d45acf74c6176e0d880bab1d7d718fe891"
|
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-9.2.1.tgz#599dc9d45acf74c6176e0d880bab1d7d718fe891"
|
||||||
integrity sha512-Obv7ycoCTG51N7y175StI9BlAXrmgZrFhZOb0/PyjHBher/NmsdBgbbQ1Inhq+gIhz6+7Gb+jWF2Vqi7Mf1xnQ==
|
integrity sha512-Obv7ycoCTG51N7y175StI9BlAXrmgZrFhZOb0/PyjHBher/NmsdBgbbQ1Inhq+gIhz6+7Gb+jWF2Vqi7Mf1xnQ==
|
||||||
|
|
||||||
|
supports-preserve-symlinks-flag@^1.0.0:
|
||||||
|
version "1.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09"
|
||||||
|
integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==
|
||||||
|
|
||||||
svg-parser@^2.0.2:
|
svg-parser@^2.0.2:
|
||||||
version "2.0.4"
|
version "2.0.4"
|
||||||
resolved "https://registry.yarnpkg.com/svg-parser/-/svg-parser-2.0.4.tgz#fdc2e29e13951736140b76cb122c8ee6630eb6b5"
|
resolved "https://registry.yarnpkg.com/svg-parser/-/svg-parser-2.0.4.tgz#fdc2e29e13951736140b76cb122c8ee6630eb6b5"
|
||||||
|
Reference in New Issue
Block a user