diff --git a/.eslintrc.js b/.eslintrc.js
index 45efe38..58b06b5 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -3,7 +3,8 @@ module.exports = {
"next/core-web-vitals",
"prettier",
"plugin:mdx/recommended",
- "plugin:react/recommended"
+ "plugin:react/recommended",
+ "plugin:react-hooks/recommended",
],
"ignorePatterns": [
// imported components from shadcn
diff --git a/components/blog-post.tsx b/components/blog-post.tsx
index c4584c4..774e901 100644
--- a/components/blog-post.tsx
+++ b/components/blog-post.tsx
@@ -1,15 +1,19 @@
import { allImages } from '@/build/fixtures/images';
-import { userProfiles } from '@/lib/data';
import CommentSection from '@/components/giscus-comments';
-import { websiteConfig } from '@/lib/data';
+import {
+ Popover,
+ PopoverContent,
+ PopoverTrigger,
+} from "@/components/ui/popover";
+import { userProfiles, websiteConfig } from '@/lib/data';
import { cn, dayjsTz } from '@/lib/utils';
+import { GithubIcon, LinkIcon, LinkedinIcon, MailIcon, XIcon } from 'lucide-react';
import { useRouter } from 'next/router';
import { Link, useConfig } from "nextra-theme-docs";
import React from 'react';
+import { SubscribeDialog } from './blog';
import Picture from './picture';
-import { GithubIcon, LinkIcon, LinkedinIcon, MailIcon, XIcon } from 'lucide-react';
import { Separator } from './ui/separator';
-import { SubscribeDialog } from './blog';
// Reference for styling: https://github.com/vercel/turbo/blob/22585c9dcc23eb010ab01f177394358af03210d7/docs/pages/blog/turbo-1-10-0.mdx
@@ -41,7 +45,7 @@ export function Avatar({ username }: { username: string }) {
return (
- Name
@@ -90,8 +94,38 @@ export function BlogPostHeader() {
const { locale = websiteConfig.default_locale } = useRouter()
const dateObj = date && timezone && dayjsTz(date, timezone).toDate()
+ let titleImageComponent;
+ if (frontMatter.title_image) {
+ const titleImage = allImages[frontMatter.title_image];
+ if (!titleImage) {
+ throw new Error(`No image found for title_image: ${frontMatter.title_image}`);
+ }
+ const titleImageAttribution = frontMatter.title_image_attribution;
+ if (!titleImageAttribution) {
+ throw new Error(`No attribution found for title_image: ${frontMatter.title_image}`);
+ }
+
+ titleImageComponent = (
+
+
+
+
+
+
+ {titleImageAttribution}
+
+
+
+ )
+ }
+
return (
<>
+ {titleImageComponent}
{title}
{/* suppressHydrationWarning is used to prevent warnings due to differing server/client locales */}
diff --git a/components/blog.tsx b/components/blog.tsx
index baad4ad..5c48b1d 100644
--- a/components/blog.tsx
+++ b/components/blog.tsx
@@ -1,3 +1,4 @@
+import { allImages } from '@/build/fixtures/images';
import {
AlertDialog,
AlertDialogAction,
@@ -6,31 +7,32 @@ import {
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle
-} from "@/components/ui/alert-dialog"
+} from "@/components/ui/alert-dialog";
import {
Form,
FormControl,
FormField,
FormItem,
FormMessage
-} from "@/components/ui/form"
-import { websiteConfig } from '@/lib/data'
-import { dayjsTz } from '@/lib/utils'
-import { zodResolver } from "@hookform/resolvers/zod"
-import { useRouter } from 'next/router'
-import { MdxFile } from "nextra"
-import { Link } from "nextra-theme-docs"
-import { getPagesUnderRoute } from "nextra/context"
-import { useState } from "react"
-import { useForm } from "react-hook-form"
-import { z } from "zod"
-import { Button } from "./ui/button"
-import { Input } from "./ui/input"
+} from "@/components/ui/form";
+import { websiteConfig } from '@/lib/data';
+import { dayjsTz } from '@/lib/utils';
+import { zodResolver } from "@hookform/resolvers/zod";
+import { useRouter } from 'next/router';
+import { MdxFile } from "nextra";
+import { Link } from "nextra-theme-docs";
+import { getPagesUnderRoute } from "nextra/context";
+import { useState } from "react";
+import { useForm } from "react-hook-form";
+import { z } from "zod";
+import Picture from "./picture";
+import { Button } from "./ui/button";
+import { Input } from "./ui/input";
// Header and Index derived from https://github.com/vercel/turbo/blob/66196a70d02cddc8899ed1423684b1f716aa310e/docs/pages/blog.mdx
export function BlogHeader() {
return (
-
+
Breadcrumbs
@@ -44,37 +46,67 @@ export function BlogHeader() {
export function BlogIndex() {
const { locale = websiteConfig.default_locale } = useRouter()
- return getPagesUnderRoute("/blog").map((page) => {
- const frontMatter = (page as MdxFile).frontMatter
- const { date, timezone } = frontMatter || {}
+ const items = getPagesUnderRoute("/blog").map((page) => {
+ const frontMatter = (page as MdxFile).frontMatter || {}
+ if (frontMatter.hidden) {
+ return null
+ }
+
+ const { date, timezone } = frontMatter
const dateObj = date && timezone && dayjsTz(date, timezone).toDate()
+ let titleImageComponent;
+ if (frontMatter.title_image) {
+ const titleImage = allImages[frontMatter.title_image];
+ if (!titleImage) {
+ throw new Error(`No image found for title_image: ${frontMatter.title_image}`);
+ }
+ const titleImageAttribution = frontMatter.title_image_attribution;
+ if (!titleImageAttribution) {
+ throw new Error(`No attribution found for title_image: ${frontMatter.title_image}`);
+ }
+
+ titleImageComponent = (
+
+ )
+ }
+
return (
-
-
- {page.meta?.title || frontMatter?.title || page.name}
-
-
- {frontMatter?.description}{" "}
-
- {"Read more →"}
-
-
- {dateObj ? (
-
- {/* suppressHydrationWarning is used to prevent warnings due to differing server/client locales */}
-
+
+
+
+ {page.meta?.title || frontMatter.title || page.name}
+
+
+ {frontMatter.description}{" "}
+
+ {"Read more →"}
+
- ) : null}
+ {dateObj ? (
+
+ {/* suppressHydrationWarning is used to prevent warnings due to differing server/client locales */}
+
+
+ ) : null}
+
+
{titleImageComponent}
);
- });
+ })
+
+ return
{items}
}
const subscribeFormSchema = z.object({
diff --git a/components/picture.tsx b/components/picture.tsx
index 85d0e8c..c4d9738 100644
--- a/components/picture.tsx
+++ b/components/picture.tsx
@@ -13,12 +13,14 @@ import { getImageBlurSvg } from "@/lib/image-blur-svg";
export default function Picture({
image,
alt,
- className = "",
+ wrapperClassName = "",
+ imgClassName = "",
style = {},
}: {
image: WATcloudStaticImage;
alt: string;
- className?: string;
+ wrapperClassName?: string;
+ imgClassName?: string;
style?: React.CSSProperties;
}) {
// The blur logic is derived from https://github.com/vercel/next.js/blob/98be3ba23ea65ac5b581999d79a1093f147b46f0/packages/next/src/shared/lib/get-img-props.ts#L626
@@ -46,7 +48,7 @@ export default function Picture({
};
return (
-