mirror of
https://github.com/jakejarvis/jarv.is.git
synced 2025-06-30 22:06:38 -04:00
organize types a bit more sanely & bump deps
This commit is contained in:
@ -1,71 +1,76 @@
|
|||||||
|
import Link from "next/link";
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
import { format } from "date-fns";
|
import { format } from "date-fns";
|
||||||
import HitCounter from "../HitCounter/HitCounter";
|
import HitCounter from "../HitCounter/HitCounter";
|
||||||
|
import NoteTitle from "../NoteTitle/NoteTitle";
|
||||||
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";
|
||||||
import type { NoteMetaType } from "../../types";
|
import type { NoteType } from "../../types";
|
||||||
|
|
||||||
import styles from "./NoteMeta.module.css";
|
import styles from "./NoteMeta.module.css";
|
||||||
import Link from "next/link";
|
|
||||||
|
|
||||||
export type NoteMetaProps = Pick<NoteMetaType, "slug" | "date" | "title" | "tags">;
|
export type NoteMetaProps = Pick<NoteType["frontMatter"], "slug" | "date" | "title" | "htmlTitle" | "tags">;
|
||||||
|
|
||||||
const NoteMeta = ({ slug, date, title, tags = [] }: NoteMetaProps) => (
|
const NoteMeta = ({ slug, date, title, htmlTitle, tags = [] }: NoteMetaProps) => (
|
||||||
<div className={styles.meta}>
|
<>
|
||||||
<div className={styles.meta_item}>
|
<div className={styles.meta}>
|
||||||
<Link
|
<div className={styles.meta_item}>
|
||||||
href={{
|
<Link
|
||||||
pathname: "/notes/[slug]/",
|
href={{
|
||||||
query: { slug: slug },
|
pathname: "/notes/[slug]/",
|
||||||
}}
|
query: { slug },
|
||||||
>
|
}}
|
||||||
<a className={styles.date_link}>
|
>
|
||||||
|
<a className={styles.date_link}>
|
||||||
|
<span>
|
||||||
|
<DateIcon className={styles.icon} />
|
||||||
|
</span>
|
||||||
|
<span title={format(new Date(date), "PPppp")}>{format(new Date(date), "MMMM d, yyyy")}</span>
|
||||||
|
</a>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{tags.length > 0 && (
|
||||||
|
<div className={classNames(styles.meta_item, styles.tags)}>
|
||||||
<span>
|
<span>
|
||||||
<DateIcon className={styles.icon} />
|
<TagIcon className={styles.icon} />
|
||||||
</span>
|
</span>
|
||||||
<span title={format(new Date(date), "PPppp")}>{format(new Date(date), "MMMM d, yyyy")}</span>
|
{tags.map((tag) => (
|
||||||
|
<span key={tag} className={styles.tag}>
|
||||||
|
{tag}
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className={styles.meta_item}>
|
||||||
|
<a
|
||||||
|
className={styles.edit_link}
|
||||||
|
href={`https://github.com/${config.githubRepo}/blob/main/notes/${slug}.mdx`}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
title={`Edit "${title}" on GitHub`}
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
<EditIcon className={styles.icon} />
|
||||||
|
</span>
|
||||||
|
<span>Improve This Post</span>
|
||||||
</a>
|
</a>
|
||||||
</Link>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
{tags.length > 0 && (
|
{/* only count hits on production site */}
|
||||||
<div className={classNames(styles.meta_item, styles.tags)}>
|
{process.env.NEXT_PUBLIC_VERCEL_ENV === "production" && (
|
||||||
<span>
|
<div className={classNames(styles.meta_item, styles.views)}>
|
||||||
<TagIcon className={styles.icon} />
|
<span>
|
||||||
</span>
|
<ViewsIcon className={styles.icon} />
|
||||||
{tags.map((tag) => (
|
|
||||||
<span key={tag} className={styles.tag}>
|
|
||||||
{tag}
|
|
||||||
</span>
|
</span>
|
||||||
))}
|
<HitCounter slug={`notes/${slug}`} />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className={styles.meta_item}>
|
|
||||||
<a
|
|
||||||
className={styles.edit_link}
|
|
||||||
href={`https://github.com/${config.githubRepo}/blob/main/notes/${slug}.mdx`}
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
title={`Edit "${title}" on GitHub`}
|
|
||||||
>
|
|
||||||
<span>
|
|
||||||
<EditIcon className={styles.icon} />
|
|
||||||
</span>
|
|
||||||
<span>Improve This Post</span>
|
|
||||||
</a>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* only count hits on production site */}
|
<NoteTitle slug={slug} htmlTitle={htmlTitle || title} />
|
||||||
{process.env.NEXT_PUBLIC_VERCEL_ENV === "production" && (
|
</>
|
||||||
<div className={classNames(styles.meta_item, styles.views)}>
|
|
||||||
<span>
|
|
||||||
<ViewsIcon className={styles.icon} />
|
|
||||||
</span>
|
|
||||||
<HitCounter slug={`notes/${slug}`} />
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
|
|
||||||
export default NoteMeta;
|
export default NoteMeta;
|
||||||
|
@ -1,17 +1,17 @@
|
|||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
import type { NoteMetaType } from "../../types";
|
import type { NoteType } from "../../types";
|
||||||
|
|
||||||
import styles from "./NoteTitle.module.css";
|
import styles from "./NoteTitle.module.css";
|
||||||
|
|
||||||
export type NoteTitleProps = Pick<NoteMetaType, "slug" | "htmlTitle"> & JSX.IntrinsicElements["h1"];
|
export type NoteTitleProps = Pick<NoteType["frontMatter"], "slug" | "htmlTitle"> & JSX.IntrinsicElements["h1"];
|
||||||
|
|
||||||
const NoteTitle = ({ slug, htmlTitle, className, ...rest }: NoteTitleProps) => (
|
const NoteTitle = ({ slug, htmlTitle, className, ...rest }: NoteTitleProps) => (
|
||||||
<h1 className={classNames(styles.title, className)} {...rest}>
|
<h1 className={classNames(styles.title, className)} {...rest}>
|
||||||
<Link
|
<Link
|
||||||
href={{
|
href={{
|
||||||
pathname: "/notes/[slug]/",
|
pathname: "/notes/[slug]/",
|
||||||
query: { slug: slug },
|
query: { slug },
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<a className={styles.link} dangerouslySetInnerHTML={{ __html: htmlTitle }} />
|
<a className={styles.link} dangerouslySetInnerHTML={{ __html: htmlTitle }} />
|
||||||
|
@ -1,17 +1,17 @@
|
|||||||
import { format } from "date-fns";
|
import { format } from "date-fns";
|
||||||
import Link from "../Link/Link";
|
import Link from "../Link/Link";
|
||||||
import type { NoteMetaType } from "../../types";
|
import type { NoteType } from "../../types";
|
||||||
|
|
||||||
import styles from "./NotesList.module.css";
|
import styles from "./NotesList.module.css";
|
||||||
|
|
||||||
export type NotesListProps = {
|
export type NotesListProps = {
|
||||||
notesByYear: Record<string, NoteMetaType[]>;
|
notesByYear: Record<string, NoteType["frontMatter"][]>;
|
||||||
};
|
};
|
||||||
|
|
||||||
const NotesList = ({ notesByYear }: NotesListProps) => {
|
const NotesList = ({ notesByYear }: NotesListProps) => {
|
||||||
const sections = [];
|
const sections = [];
|
||||||
|
|
||||||
Object.entries(notesByYear).forEach(([year, notes]: [string, NoteMetaType[]]) => {
|
Object.entries(notesByYear).forEach(([year, notes]: [string, NoteType["frontMatter"][]]) => {
|
||||||
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>
|
||||||
@ -23,7 +23,7 @@ const NotesList = ({ notesByYear }: NotesListProps) => {
|
|||||||
<Link
|
<Link
|
||||||
href={{
|
href={{
|
||||||
pathname: "/notes/[slug]/",
|
pathname: "/notes/[slug]/",
|
||||||
query: { slug: slug },
|
query: { slug },
|
||||||
}}
|
}}
|
||||||
dangerouslySetInnerHTML={{ __html: htmlTitle }}
|
dangerouslySetInnerHTML={{ __html: htmlTitle }}
|
||||||
/>
|
/>
|
||||||
|
@ -2,11 +2,11 @@ import classNames from "classnames";
|
|||||||
import { intlFormat, formatDistanceToNowStrict } from "date-fns";
|
import { intlFormat, formatDistanceToNowStrict } from "date-fns";
|
||||||
import Link from "../Link/Link";
|
import Link from "../Link/Link";
|
||||||
import { StarOcticon, ForkOcticon } from "../Icons";
|
import { StarOcticon, ForkOcticon } from "../Icons";
|
||||||
import type { RepoType } from "../../types";
|
import type { RepositoryType } from "../../types";
|
||||||
|
|
||||||
import styles from "./RepositoryCard.module.css";
|
import styles from "./RepositoryCard.module.css";
|
||||||
|
|
||||||
export type RepositoryCardProps = RepoType & {
|
export type RepositoryCardProps = RepositoryType & {
|
||||||
className?: string;
|
className?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -9,7 +9,7 @@ export type TweetEmbedProps = {
|
|||||||
const TweetEmbed = ({ id, className, options }: TweetEmbedProps) => (
|
const TweetEmbed = ({ id, className, options }: TweetEmbedProps) => (
|
||||||
<Tweet
|
<Tweet
|
||||||
className={className}
|
className={className}
|
||||||
id={id}
|
tweetId={id}
|
||||||
options={{
|
options={{
|
||||||
dnt: true,
|
dnt: true,
|
||||||
align: "center",
|
align: "center",
|
||||||
|
@ -16,7 +16,7 @@ import rehypeSlug from "rehype-slug";
|
|||||||
import rehypePrism from "rehype-prism-plus";
|
import rehypePrism from "rehype-prism-plus";
|
||||||
|
|
||||||
import type { MinifyOptions } from "terser";
|
import type { MinifyOptions } from "terser";
|
||||||
import type { NoteMetaType, NoteType } from "../types";
|
import type { NoteType } from "../types";
|
||||||
|
|
||||||
// returns all .mdx files in NOTES_DIR (without .mdx extension)
|
// returns all .mdx files in NOTES_DIR (without .mdx extension)
|
||||||
export const getNoteSlugs = () =>
|
export const getNoteSlugs = () =>
|
||||||
@ -26,7 +26,7 @@ export const getNoteSlugs = () =>
|
|||||||
.map((noteFile) => noteFile.replace(/\.mdx$/, ""));
|
.map((noteFile) => noteFile.replace(/\.mdx$/, ""));
|
||||||
|
|
||||||
// returns front matter and/or *raw* markdown contents of a given slug
|
// returns front matter and/or *raw* markdown contents of a given slug
|
||||||
export const getNoteData = (slug: string): { frontMatter: NoteMetaType; content: string } => {
|
export const getNoteData = (slug: string): Omit<NoteType, "source"> & { content: string } => {
|
||||||
const fullPath = path.join(process.cwd(), NOTES_DIR, `${slug}.mdx`);
|
const fullPath = path.join(process.cwd(), NOTES_DIR, `${slug}.mdx`);
|
||||||
const rawContent = fs.readFileSync(fullPath, "utf8");
|
const rawContent = fs.readFileSync(fullPath, "utf8");
|
||||||
const { data, content } = matter(rawContent);
|
const { data, content } = matter(rawContent);
|
||||||
@ -47,7 +47,7 @@ export const getNoteData = (slug: string): { frontMatter: NoteMetaType; content:
|
|||||||
// return both the parsed YAML front matter (with a few amendments) and the raw, unparsed markdown content
|
// return both the parsed YAML front matter (with a few amendments) and the raw, unparsed markdown content
|
||||||
return {
|
return {
|
||||||
frontMatter: {
|
frontMatter: {
|
||||||
...(data as Omit<NoteMetaType, "slug" | "title" | "htmlTitle" | "permalink" | "date" | "readingMins">),
|
...(data as Omit<NoteType["frontMatter"], "slug" | "title" | "htmlTitle" | "permalink" | "date" | "readingMins">),
|
||||||
// zero markdown title:
|
// zero markdown title:
|
||||||
title: removeMarkdown(data.title),
|
title: removeMarkdown(data.title),
|
||||||
// parsed markdown title:
|
// parsed markdown title:
|
||||||
@ -103,4 +103,4 @@ export const getNote = async (slug: string): Promise<NoteType> => {
|
|||||||
export const getAllNotes = () =>
|
export const getAllNotes = () =>
|
||||||
getNoteSlugs()
|
getNoteSlugs()
|
||||||
.map((slug) => getNoteData(slug).frontMatter)
|
.map((slug) => getNoteData(slug).frontMatter)
|
||||||
.sort((note1: NoteMetaType, note2: NoteMetaType) => (note1.date > note2.date ? -1 : 1));
|
.sort((note1: NoteType["frontMatter"], note2: NoteType["frontMatter"]) => (note1.date > note2.date ? -1 : 1));
|
||||||
|
@ -38,14 +38,6 @@ module.exports = (phase, { defaultConfig }) => {
|
|||||||
config.module.rules.push({
|
config.module.rules.push({
|
||||||
test: /\.svg$/,
|
test: /\.svg$/,
|
||||||
issuer: { and: [/\.(js|ts)x?$/] },
|
issuer: { and: [/\.(js|ts)x?$/] },
|
||||||
include: [
|
|
||||||
path.resolve(__dirname, "components/icons"),
|
|
||||||
// slight workaround to grab svg files from these packages directly instead of through their exports:
|
|
||||||
path.resolve(__dirname, "node_modules/@primer/octicons/build/svg"),
|
|
||||||
path.resolve(__dirname, "node_modules/feather-icons/dist/icons"),
|
|
||||||
path.resolve(__dirname, "node_modules/simple-icons/icons"),
|
|
||||||
path.resolve(__dirname, "node_modules/twemoji/assets/svg"),
|
|
||||||
],
|
|
||||||
use: [
|
use: [
|
||||||
{
|
{
|
||||||
loader: "@svgr/webpack",
|
loader: "@svgr/webpack",
|
||||||
@ -58,6 +50,15 @@ module.exports = (phase, { defaultConfig }) => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
include: [
|
||||||
|
// allow processing images from these packages directly instead of through their different exports, and leave
|
||||||
|
// other static imports of SVGs alone.
|
||||||
|
// see: ./components/Icons/index.ts
|
||||||
|
path.resolve(__dirname, "node_modules/@primer/octicons/build/svg"),
|
||||||
|
path.resolve(__dirname, "node_modules/feather-icons/dist/icons"),
|
||||||
|
path.resolve(__dirname, "node_modules/simple-icons/icons"),
|
||||||
|
path.resolve(__dirname, "node_modules/twemoji/assets/svg"),
|
||||||
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
return config;
|
return config;
|
||||||
|
27
package.json
27
package.json
@ -14,7 +14,7 @@
|
|||||||
"url": "https://github.com/jakejarvis/jarv.is.git"
|
"url": "https://github.com/jakejarvis/jarv.is.git"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "cross-env NODE_OPTIONS='--inspect' next-remote-watch ./notes",
|
"dev": "cross-env NODE_OPTIONS='--inspect' next dev",
|
||||||
"build": "next build",
|
"build": "next build",
|
||||||
"analyze": "cross-env ANALYZE=true next build",
|
"analyze": "cross-env ANALYZE=true next build",
|
||||||
"lint": "run-s lint:*",
|
"lint": "run-s lint:*",
|
||||||
@ -23,7 +23,7 @@
|
|||||||
"lint:prettier": "prettier --check ."
|
"lint:prettier": "prettier --check ."
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@fontsource/comic-neue": "4.5.2",
|
"@fontsource/comic-neue": "4.5.3",
|
||||||
"@fontsource/inter": "4.5.4",
|
"@fontsource/inter": "4.5.4",
|
||||||
"@fontsource/roboto-mono": "4.5.3",
|
"@fontsource/roboto-mono": "4.5.3",
|
||||||
"@giscus/react": "^1.1.2",
|
"@giscus/react": "^1.1.2",
|
||||||
@ -47,36 +47,36 @@
|
|||||||
"gray-matter": "^4.0.3",
|
"gray-matter": "^4.0.3",
|
||||||
"is-absolute-url": "^4.0.1",
|
"is-absolute-url": "^4.0.1",
|
||||||
"markdown-to-jsx": "^7.1.6",
|
"markdown-to-jsx": "^7.1.6",
|
||||||
"modern-normalize": "^1.1.0",
|
"modern-normalize": "github:sindresorhus/modern-normalize#b59ec0d3d8654cbb6843bc9ea45aef5f1d680108",
|
||||||
"next": "12.1.0",
|
"next": "12.1.0",
|
||||||
"next-compose-plugins": "^2.2.1",
|
"next-compose-plugins": "^2.2.1",
|
||||||
"next-mdx-remote": "^4.0.0",
|
"next-mdx-remote": "^4.0.0",
|
||||||
"next-seo": "^5.1.0",
|
"next-seo": "^5.1.0",
|
||||||
"next-sitemap": "^2.4.3",
|
"next-sitemap": "^2.5.1",
|
||||||
"next-themes": "^0.0.15",
|
"next-themes": "^0.0.15",
|
||||||
"next-transpile-modules": "^9.0.0",
|
"next-transpile-modules": "^9.0.0",
|
||||||
"node-fetch": "^3.2.0",
|
"node-fetch": "^3.2.0",
|
||||||
"p-retry": "^5.0.0",
|
"p-retry": "^5.0.0",
|
||||||
"prop-types": "^15.8.1",
|
"prop-types": "^15.8.1",
|
||||||
"query-string": "^7.1.1",
|
"query-string": "^7.1.1",
|
||||||
"react": "^17.0.2",
|
"react": "17.0.2",
|
||||||
"react-dom": "^17.0.2",
|
"react-dom": "17.0.2",
|
||||||
"react-gist": "^1.2.4",
|
"react-gist": "^1.2.4",
|
||||||
"react-innertext": "^1.1.5",
|
"react-innertext": "^1.1.5",
|
||||||
"react-intersection-observer": "^8.33.1",
|
"react-intersection-observer": "^8.33.1",
|
||||||
"react-is": "^17.0.2",
|
"react-is": "17.0.2",
|
||||||
"react-player": "^2.9.0",
|
"react-player": "^2.9.0",
|
||||||
"react-textarea-autosize": "^8.3.3",
|
"react-textarea-autosize": "^8.3.3",
|
||||||
"react-tweet-embed": "^1.3.1",
|
"react-tweet-embed": "^2.0.0",
|
||||||
"reading-time": "^1.5.0",
|
"reading-time": "^1.5.0",
|
||||||
"rehype-prism-plus": "^1.3.1",
|
"rehype-prism-plus": "^1.3.1",
|
||||||
"rehype-slug": "^5.0.1",
|
"rehype-slug": "^5.0.1",
|
||||||
"remark-gfm": "^3.0.1",
|
"remark-gfm": "^3.0.1",
|
||||||
"remove-markdown": "^0.3.0",
|
"remove-markdown": "^0.3.0",
|
||||||
"sanitize-html": "^2.7.0",
|
"sanitize-html": "^2.7.0",
|
||||||
"simple-icons": "^6.10.0",
|
"simple-icons": "^6.11.0",
|
||||||
"swr": "^1.2.2",
|
"swr": "^1.2.2",
|
||||||
"terser": "^5.10.0",
|
"terser": "^5.11.0",
|
||||||
"twemoji": "github:twitter/twemoji#v13.1.0"
|
"twemoji": "github:twitter/twemoji#v13.1.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@ -89,8 +89,8 @@
|
|||||||
"@types/react-is": "^17.0.3",
|
"@types/react-is": "^17.0.3",
|
||||||
"@types/remove-markdown": "^0.3.1",
|
"@types/remove-markdown": "^0.3.1",
|
||||||
"@types/sanitize-html": "^2.6.2",
|
"@types/sanitize-html": "^2.6.2",
|
||||||
"@typescript-eslint/eslint-plugin": "^5.12.0",
|
"@typescript-eslint/eslint-plugin": "^5.12.1",
|
||||||
"@typescript-eslint/parser": "^5.12.0",
|
"@typescript-eslint/parser": "^5.12.1",
|
||||||
"cross-env": "^7.0.3",
|
"cross-env": "^7.0.3",
|
||||||
"eslint": "~8.9.0",
|
"eslint": "~8.9.0",
|
||||||
"eslint-config-next": "12.1.0",
|
"eslint-config-next": "12.1.0",
|
||||||
@ -99,11 +99,10 @@
|
|||||||
"eslint-plugin-mdx": "~1.16.0",
|
"eslint-plugin-mdx": "~1.16.0",
|
||||||
"eslint-plugin-prettier": "~4.0.0",
|
"eslint-plugin-prettier": "~4.0.0",
|
||||||
"lint-staged": "^12.3.4",
|
"lint-staged": "^12.3.4",
|
||||||
"next-remote-watch": "^1.0.0",
|
|
||||||
"npm-run-all": "^4.1.5",
|
"npm-run-all": "^4.1.5",
|
||||||
"prettier": "^2.5.1",
|
"prettier": "^2.5.1",
|
||||||
"simple-git-hooks": "^2.7.0",
|
"simple-git-hooks": "^2.7.0",
|
||||||
"stylelint": "~14.5.1",
|
"stylelint": "~14.5.3",
|
||||||
"stylelint-config-prettier": "~9.0.3",
|
"stylelint-config-prettier": "~9.0.3",
|
||||||
"stylelint-prettier": "~2.0.0",
|
"stylelint-prettier": "~2.0.0",
|
||||||
"typescript": "^4.5.5"
|
"typescript": "^4.5.5"
|
||||||
|
@ -72,7 +72,7 @@ const App = ({ Component, pageProps }: AppProps) => {
|
|||||||
<>
|
<>
|
||||||
{/* static asset preloads */}
|
{/* static asset preloads */}
|
||||||
<Head>
|
<Head>
|
||||||
{/* TODO: these hrefs will change at some point (and possibly unpredictably). find a better way... */}
|
{/* TODO: these hrefs will change (unpredictably?) at some point. find a safer way to get them from webpack. */}
|
||||||
<link
|
<link
|
||||||
rel="preload"
|
rel="preload"
|
||||||
as="font"
|
as="font"
|
||||||
@ -89,7 +89,7 @@ const App = ({ Component, pageProps }: AppProps) => {
|
|||||||
/>
|
/>
|
||||||
</Head>
|
</Head>
|
||||||
|
|
||||||
{/* all SEO config is in ./lib/seo.ts except for canonical URLs, which require access to next router */}
|
{/* all SEO config is in ../lib/seo.ts except for canonical URLs, which require access to next router */}
|
||||||
<DefaultSeo
|
<DefaultSeo
|
||||||
{...defaultSeo}
|
{...defaultSeo}
|
||||||
canonical={canonical}
|
canonical={canonical}
|
||||||
@ -103,6 +103,7 @@ const App = ({ Component, pageProps }: AppProps) => {
|
|||||||
/>
|
/>
|
||||||
<SocialProfileJsonLd {...socialProfileJsonLd} />
|
<SocialProfileJsonLd {...socialProfileJsonLd} />
|
||||||
|
|
||||||
|
{/* NOTE: this *must* come last in this fragment */}
|
||||||
<ThemeProvider>{getLayout(<Component {...pageProps} />)}</ThemeProvider>
|
<ThemeProvider>{getLayout(<Component {...pageProps} />)}</ThemeProvider>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
@ -4,7 +4,6 @@ import { MDXRemote } from "next-mdx-remote";
|
|||||||
import { htmlEscape } from "escape-goat";
|
import { htmlEscape } from "escape-goat";
|
||||||
import Content from "../../components/Content/Content";
|
import Content from "../../components/Content/Content";
|
||||||
import NoteMeta from "../../components/NoteMeta/NoteMeta";
|
import NoteMeta from "../../components/NoteMeta/NoteMeta";
|
||||||
import NoteTitle from "../../components/NoteTitle/NoteTitle";
|
|
||||||
import Comments from "../../components/Comments/Comments";
|
import Comments from "../../components/Comments/Comments";
|
||||||
import * as mdxComponents from "../../lib/mdx-components";
|
import * as mdxComponents from "../../lib/mdx-components";
|
||||||
import { getNote, getNoteSlugs } from "../../lib/parse-notes";
|
import { getNote, getNoteSlugs } from "../../lib/parse-notes";
|
||||||
@ -51,15 +50,15 @@ const Note = ({ frontMatter, source }: NoteType) => {
|
|||||||
{...articleJsonLd}
|
{...articleJsonLd}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<NoteMeta slug={frontMatter.slug} date={frontMatter.date} title={frontMatter.title} tags={frontMatter.tags} />
|
<NoteMeta {...frontMatter} />
|
||||||
<NoteTitle slug={frontMatter.slug} htmlTitle={frontMatter.htmlTitle} />
|
|
||||||
|
|
||||||
<Content>
|
<Content>
|
||||||
{/* @ts-ignore */}
|
{/* @ts-ignore */}
|
||||||
<MDXRemote {...source} components={{ ...mdxComponents }} />
|
<MDXRemote {...source} components={{ ...mdxComponents }} />
|
||||||
</Content>
|
</Content>
|
||||||
|
|
||||||
{frontMatter.noComments !== true && (
|
{/* comments can be disabled for an individual post via `noComments: true` in its front matter */}
|
||||||
|
{!frontMatter.noComments && (
|
||||||
<InView rootMargin="140px" triggerOnce fallbackInView>
|
<InView rootMargin="140px" triggerOnce fallbackInView>
|
||||||
{({ inView, ref }) => (
|
{({ inView, ref }) => (
|
||||||
<div id="comments" ref={ref}>
|
<div id="comments" ref={ref}>
|
||||||
@ -72,8 +71,8 @@ const Note = ({ frontMatter, source }: NoteType) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getStaticProps: GetStaticProps = async ({ params }) => {
|
export const getStaticProps: GetStaticProps = async ({ params }: { params: Pick<NoteType["frontMatter"], "slug"> }) => {
|
||||||
const { frontMatter, source } = await getNote(params.slug as string);
|
const { frontMatter, source } = await getNote(params.slug);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
props: {
|
props: {
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
import { NextSeo } from "next-seo";
|
import { NextSeo } from "next-seo";
|
||||||
import { format } from "date-fns";
|
import { format } from "date-fns";
|
||||||
import Content from "../../components/Content/Content";
|
import Content from "../../components/Content/Content";
|
||||||
import NotesList from "../../components/NotesList/NotesList";
|
import NotesList, { NotesListProps } from "../../components/NotesList/NotesList";
|
||||||
import { getAllNotes } from "../../lib/parse-notes";
|
import { getAllNotes } from "../../lib/parse-notes";
|
||||||
import type { GetStaticProps } from "next";
|
import type { GetStaticProps } from "next";
|
||||||
|
|
||||||
const Notes = ({ notesByYear }) => (
|
const Notes = ({ notesByYear }: NotesListProps) => (
|
||||||
<>
|
<>
|
||||||
<NextSeo
|
<NextSeo
|
||||||
title="Notes"
|
title="Notes"
|
||||||
@ -23,9 +23,10 @@ const Notes = ({ notesByYear }) => (
|
|||||||
|
|
||||||
export const getStaticProps: GetStaticProps = async () => {
|
export const getStaticProps: GetStaticProps = async () => {
|
||||||
// parse the year of each note and group them together
|
// parse the year of each note and group them together
|
||||||
const notesByYear = {};
|
const notesByYear: NotesListProps["notesByYear"] = {};
|
||||||
|
|
||||||
getAllNotes().map((note) => {
|
getAllNotes().map((note) => {
|
||||||
const year = Number.parseInt(format(new Date(note.date), "yyyy"));
|
const year = format(new Date(note.date), "yyyy");
|
||||||
(notesByYear[year] || (notesByYear[year] = [])).push(note);
|
(notesByYear[year] || (notesByYear[year] = [])).push(note);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -7,7 +7,7 @@ import RepositoryCard from "../components/RepositoryCard/RepositoryCard";
|
|||||||
import { OctocatOcticon } from "../components/Icons";
|
import { OctocatOcticon } from "../components/Icons";
|
||||||
import { authorSocial } from "../lib/config";
|
import { authorSocial } from "../lib/config";
|
||||||
import type { GetStaticProps } from "next";
|
import type { GetStaticProps } from "next";
|
||||||
import type { RepoType } from "../types";
|
import type { RepositoryType } from "../types";
|
||||||
|
|
||||||
const Projects = ({ repos }) => (
|
const Projects = ({ repos }) => (
|
||||||
<>
|
<>
|
||||||
@ -22,7 +22,7 @@ const Projects = ({ repos }) => (
|
|||||||
|
|
||||||
<Content>
|
<Content>
|
||||||
<div className="wrapper">
|
<div className="wrapper">
|
||||||
{repos.map((repo: RepoType) => (
|
{repos.map((repo: RepositoryType) => (
|
||||||
<div key={repo.name} className="card">
|
<div key={repo.name} className="card">
|
||||||
<RepositoryCard {...repo} />
|
<RepositoryCard {...repo} />
|
||||||
</div>
|
</div>
|
||||||
@ -116,7 +116,7 @@ export const getStaticProps: GetStaticProps = async () => {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
const repos: RepoType[] = user.repositories.edges.map(({ node: repo }) => ({
|
const repos: RepositoryType[] = user.repositories.edges.map(({ node: repo }) => ({
|
||||||
name: repo.name,
|
name: repo.name,
|
||||||
url: repo.url,
|
url: repo.url,
|
||||||
description: repo.description,
|
description: repo.description,
|
||||||
|
34
types/index.d.ts
vendored
34
types/index.d.ts
vendored
@ -1,32 +1,2 @@
|
|||||||
import type { MDXRemoteSerializeResult } from "next-mdx-remote";
|
export * from "./note";
|
||||||
|
export * from "./repository";
|
||||||
export type NoteMetaType = {
|
|
||||||
title: string;
|
|
||||||
htmlTitle: string;
|
|
||||||
date: string;
|
|
||||||
slug: string;
|
|
||||||
permalink: string;
|
|
||||||
description: string;
|
|
||||||
image?: string;
|
|
||||||
tags?: string[];
|
|
||||||
readingMins?: number;
|
|
||||||
noComments?: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type NoteType = {
|
|
||||||
frontMatter: NoteMetaType;
|
|
||||||
source: MDXRemoteSerializeResult;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type RepoType = {
|
|
||||||
name: string;
|
|
||||||
url: string;
|
|
||||||
description?: string;
|
|
||||||
language?: {
|
|
||||||
name: string;
|
|
||||||
color?: string;
|
|
||||||
};
|
|
||||||
stars?: number;
|
|
||||||
forks?: number;
|
|
||||||
updatedAt: string;
|
|
||||||
};
|
|
||||||
|
19
types/note.d.ts
vendored
Normal file
19
types/note.d.ts
vendored
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import type { MDXRemoteSerializeResult } from "next-mdx-remote";
|
||||||
|
|
||||||
|
export type NoteType = {
|
||||||
|
frontMatter: {
|
||||||
|
slug: string;
|
||||||
|
permalink: string;
|
||||||
|
date: string;
|
||||||
|
title: string;
|
||||||
|
htmlTitle?: string;
|
||||||
|
description?: string;
|
||||||
|
image?: string;
|
||||||
|
tags?: string[];
|
||||||
|
readingMins?: number;
|
||||||
|
noComments?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
// the final, compiled JSX by next-mdx-remote; see lib/parse-notes.ts
|
||||||
|
source: MDXRemoteSerializeResult;
|
||||||
|
};
|
12
types/repository.d.ts
vendored
Normal file
12
types/repository.d.ts
vendored
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
export type RepositoryType = {
|
||||||
|
name: string;
|
||||||
|
url: string;
|
||||||
|
description?: string;
|
||||||
|
language?: {
|
||||||
|
name: string;
|
||||||
|
color?: string;
|
||||||
|
};
|
||||||
|
stars?: number;
|
||||||
|
forks?: number;
|
||||||
|
updatedAt: string;
|
||||||
|
};
|
Reference in New Issue
Block a user