Archived
Merge remote-tracking branch 'origin/main'
# Conflicts: # package-lock.json
This commit is contained in:
+53
-26
@@ -1,19 +1,21 @@
|
|||||||
import ShareButtons from '@/components/ShareButtons';
|
import ShareButtons from "@/components/ShareButtons";
|
||||||
import { getPostData, getSortedPostsData } from '@/lib/markdown';
|
import { getPostData, getSortedPostsData } from "@/lib/markdown";
|
||||||
import { PostData, PostMetadata } from '@/types';
|
import { PostData, PostMetadata } from "@/types";
|
||||||
import { format } from 'date-fns';
|
import { format } from "date-fns";
|
||||||
import Image from 'next/image';
|
import Image from "next/image";
|
||||||
import Link from 'next/link';
|
import Link from "next/link";
|
||||||
import React from 'react';
|
import React from "react";
|
||||||
import CommentSection from "@/components/CommentSection";
|
import CommentSection from "@/components/CommentSection";
|
||||||
import { generateMetadata as createMetadata } from '@/components/Metadata';
|
import { generateMetadata as createMetadata } from "@/components/Metadata";
|
||||||
import type { Metadata } from 'next';
|
import type { Metadata } from "next";
|
||||||
|
|
||||||
interface PageProps {
|
interface PageProps {
|
||||||
params: Promise<{ id: string }>;
|
params: Promise<{ id: string }>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function generateMetadata({ params }: PageProps): Promise<Metadata> {
|
export async function generateMetadata({
|
||||||
|
params,
|
||||||
|
}: PageProps): Promise<Metadata> {
|
||||||
const { id } = await params;
|
const { id } = await params;
|
||||||
const post = await getPostData(id);
|
const post = await getPostData(id);
|
||||||
const postUrl = `https://confessionsofgrace.com/posts/${post.id}`;
|
const postUrl = `https://confessionsofgrace.com/posts/${post.id}`;
|
||||||
@@ -21,28 +23,34 @@ export async function generateMetadata({ params }: PageProps): Promise<Metadata>
|
|||||||
return createMetadata({
|
return createMetadata({
|
||||||
title: post.title,
|
title: post.title,
|
||||||
description: post.excerpt,
|
description: post.excerpt,
|
||||||
keywords: post.tags.join(', '),
|
keywords: post.tags.join(", "),
|
||||||
image: post.coverImage,
|
image: post.coverImage,
|
||||||
url: postUrl,
|
url: postUrl,
|
||||||
type: 'article'
|
type: "article",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getPostAndMorePosts(id: string): Promise<{ post: PostData; morePosts: PostMetadata[] }> {
|
async function getPostAndMorePosts(
|
||||||
|
id: string,
|
||||||
|
): Promise<{ post: PostData; morePosts: PostMetadata[] }> {
|
||||||
const post = await getPostData(id);
|
const post = await getPostData(id);
|
||||||
const allPosts = getSortedPostsData();
|
const allPosts = getSortedPostsData();
|
||||||
|
|
||||||
// Filter out the current post and get a few related posts (by tags)
|
// Filter out the current post and get a few related posts (by tags)
|
||||||
const otherPosts = allPosts.filter(p => p.id !== post.id);
|
const otherPosts = allPosts.filter((p) => p.id !== post.id);
|
||||||
|
|
||||||
// Find posts with matching tags
|
// Find posts with matching tags
|
||||||
const relatedPosts = otherPosts
|
const relatedPosts = otherPosts
|
||||||
.filter(p => p.tags.some(tag => post.tags.includes(tag)))
|
.filter((p) => p.tags.some((tag) => post.tags.includes(tag)))
|
||||||
.slice(0, 2);
|
.slice(0, 2);
|
||||||
|
|
||||||
// If we don't have enough related posts, add recent posts
|
// If we don't have enough related posts, add recent posts
|
||||||
const morePosts = relatedPosts.length < 2
|
const morePosts =
|
||||||
? [...relatedPosts, ...otherPosts.filter(p => !relatedPosts.includes(p))].slice(0, 2)
|
relatedPosts.length < 2
|
||||||
|
? [
|
||||||
|
...relatedPosts,
|
||||||
|
...otherPosts.filter((p) => !relatedPosts.includes(p)),
|
||||||
|
].slice(0, 2)
|
||||||
: relatedPosts;
|
: relatedPosts;
|
||||||
|
|
||||||
return { post, morePosts };
|
return { post, morePosts };
|
||||||
@@ -61,7 +69,7 @@ export default async function PostPage({ params }: PageProps) {
|
|||||||
<span>{post.author}</span>
|
<span>{post.author}</span>
|
||||||
<span className="mx-2">•</span>
|
<span className="mx-2">•</span>
|
||||||
<time dateTime={post.date}>
|
<time dateTime={post.date}>
|
||||||
{format(new Date(post.date), 'MMMM d, yyyy')}
|
{format(new Date(post.date), "MMMM d, yyyy")}
|
||||||
</time>
|
</time>
|
||||||
</div>
|
</div>
|
||||||
{post.coverImage && (
|
{post.coverImage && (
|
||||||
@@ -76,7 +84,7 @@ export default async function PostPage({ params }: PageProps) {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className="flex flex-wrap gap-2 mb-6">
|
<div className="flex flex-wrap gap-2 mb-6">
|
||||||
{post.tags.map(tag => (
|
{post.tags.map((tag) => (
|
||||||
<Link
|
<Link
|
||||||
key={tag}
|
key={tag}
|
||||||
href={`/tags/${tag}`}
|
href={`/tags/${tag}`}
|
||||||
@@ -104,15 +112,21 @@ export default async function PostPage({ params }: PageProps) {
|
|||||||
<div className="mt-12 pt-6 border-t border-primary-200">
|
<div className="mt-12 pt-6 border-t border-primary-200">
|
||||||
<h2 className="text-2xl font-bold mb-6">More Posts</h2>
|
<h2 className="text-2xl font-bold mb-6">More Posts</h2>
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||||
{morePosts.slice(0, 2).map(post => (
|
{morePosts.slice(0, 2).map((post) => (
|
||||||
<div key={post.id} className="bg-white p-6 rounded-md border border-primary-200 shadow-sm">
|
<div
|
||||||
|
key={post.id}
|
||||||
|
className="bg-white p-6 rounded-md border border-primary-200 shadow-sm"
|
||||||
|
>
|
||||||
<h3 className="font-bold mb-2">
|
<h3 className="font-bold mb-2">
|
||||||
<Link href={`/posts/${post.id}`} className="hover:text-accent-dark">
|
<Link
|
||||||
|
href={`/posts/${post.id}`}
|
||||||
|
className="hover:text-accent-dark"
|
||||||
|
>
|
||||||
{post.title}
|
{post.title}
|
||||||
</Link>
|
</Link>
|
||||||
</h3>
|
</h3>
|
||||||
<p className="text-primary-600 text-sm mb-2">
|
<p className="text-primary-600 text-sm mb-2">
|
||||||
{format(new Date(post.date), 'MMMM d, yyyy')}
|
{format(new Date(post.date), "MMMM d, yyyy")}
|
||||||
</p>
|
</p>
|
||||||
<p className="text-primary-700">{post.excerpt}</p>
|
<p className="text-primary-700">{post.excerpt}</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -123,9 +137,22 @@ export default async function PostPage({ params }: PageProps) {
|
|||||||
<CommentSection postId={post.id} />
|
<CommentSection postId={post.id} />
|
||||||
|
|
||||||
<div className="mt-12 pt-6 border-t border-primary-200">
|
<div className="mt-12 pt-6 border-t border-primary-200">
|
||||||
<Link href="/public" className="text-accent-dark hover:text-accent inline-flex items-center">
|
<Link
|
||||||
<svg className="mr-2 h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
href="/public"
|
||||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M10 19l-7-7m0 0l7-7m-7 7h18" />
|
className="text-accent-dark hover:text-accent inline-flex items-center"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
className="mr-2 h-4 w-4"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
stroke="currentColor"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth="2"
|
||||||
|
d="M10 19l-7-7m0 0l7-7m-7 7h18"
|
||||||
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
Back to all posts
|
Back to all posts
|
||||||
</Link>
|
</Link>
|
||||||
|
|||||||
+20
-17
@@ -10,18 +10,21 @@ interface MetaProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function generateMetadata({
|
export function generateMetadata({
|
||||||
title = 'Confessions of Grace',
|
title = "Confessions of Grace",
|
||||||
description = 'A blog dedicated to exploring the doctrines of grace and Reformed theology.',
|
description = "A blog dedicated to exploring the doctrines of grace and Reformed theology.",
|
||||||
keywords = 'reformed theology, doctrines of grace, christianity, calvinism, theology',
|
keywords = "reformed theology, doctrines of grace, christianity, calvinism, theology",
|
||||||
image = '/images/og-default.jpg',
|
image = "/images/og-default.jpg",
|
||||||
url = 'https://confessionsofgrace.com',
|
url = "https://confessionsofgrace.com",
|
||||||
type = 'website'
|
type = "website",
|
||||||
}: MetaProps = {}): Metadata {
|
}: MetaProps = {}): Metadata {
|
||||||
const siteTitle = title === 'Confessions of Grace'
|
const siteTitle =
|
||||||
? 'Confessions of Grace | Confessing Christ. Rejoicing in Grace.'
|
title === "Confessions of Grace"
|
||||||
|
? "Confessions of Grace | Confessing Christ. Rejoicing in Grace."
|
||||||
: `${title} | Confessions of Grace`;
|
: `${title} | Confessions of Grace`;
|
||||||
|
|
||||||
const fullImageUrl = image.startsWith('http') ? image : `https://www.confessionsofgrace.com${image}`;
|
const fullImageUrl = image.startsWith("http")
|
||||||
|
? image
|
||||||
|
: `https://confessionsofgrace.com${image}`;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
title: siteTitle,
|
title: siteTitle,
|
||||||
@@ -35,21 +38,21 @@ export function generateMetadata({
|
|||||||
images: [
|
images: [
|
||||||
{
|
{
|
||||||
url: fullImageUrl,
|
url: fullImageUrl,
|
||||||
alt: title || 'Confessions of Grace'
|
alt: title || "Confessions of Grace",
|
||||||
}
|
},
|
||||||
],
|
],
|
||||||
locale: 'en_US',
|
locale: "en_US",
|
||||||
siteName: 'Confessions of Grace'
|
siteName: "Confessions of Grace",
|
||||||
},
|
},
|
||||||
twitter: {
|
twitter: {
|
||||||
card: 'summary_large_image',
|
card: "summary_large_image",
|
||||||
title: siteTitle,
|
title: siteTitle,
|
||||||
description,
|
description,
|
||||||
images: [fullImageUrl]
|
images: [fullImageUrl],
|
||||||
},
|
},
|
||||||
alternates: {
|
alternates: {
|
||||||
canonical: url
|
canonical: url,
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user