diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 00000000..b90a368f --- /dev/null +++ b/.prettierignore @@ -0,0 +1,2 @@ +node_modules +.next diff --git a/app/(main)/blog/BlogPostCard.tsx b/app/(main)/blog/BlogPostCard.tsx new file mode 100644 index 00000000..1e0e20c4 --- /dev/null +++ b/app/(main)/blog/BlogPostCard.tsx @@ -0,0 +1,74 @@ +import Image from 'next/image' +import Link from 'next/link' + +import { + CalendarIcon, + CursorClickIcon, + HourglassIcon, + ScriptIcon, +} from '~/assets' +import { prettifyNumber } from '~/lib/math' +import { type Post } from '~/sanity/schemas/post' + +export function BlogPostCard({ post, views }: { post: Post; views: number }) { + const { title, slug, mainImage, publishedAt, categories, readingTime } = post + + return ( + +
+ +
+ +

+ {title} +

+ + + + + + + {Intl.DateTimeFormat('zh').format(new Date(publishedAt))} + + + + + + {categories.join(', ')} + + + + + + {prettifyNumber(views, true)} + + + + + {readingTime.toFixed(0)}分钟阅读 + + + +
+ + ) +} diff --git a/app/(main)/blog/BlogPosts.tsx b/app/(main)/blog/BlogPosts.tsx index 2ed83cd1..2ce58160 100644 --- a/app/(main)/blog/BlogPosts.tsx +++ b/app/(main)/blog/BlogPosts.tsx @@ -1,18 +1,10 @@ -import Image from 'next/image' -import Link from 'next/link' - -import { - CalendarIcon, - CursorClickIcon, - HourglassIcon, - ScriptIcon, -} from '~/assets' import { kvKeys } from '~/config/kv' import { env } from '~/env.mjs' -import { prettifyNumber } from '~/lib/math' import { redis } from '~/lib/redis' import { getLatestBlogPosts } from '~/sanity/queries' +import { BlogPostCard } from './BlogPostCard' + export async function BlogPosts({ limit = 5 }) { const posts = await getLatestBlogPosts({ limit, forDisplay: true }) const postIdKeys = posts.map(({ _id }) => kvKeys.postViews(_id)) @@ -26,69 +18,9 @@ export async function BlogPosts({ limit = 5 }) { return ( <> - {posts.map( - ( - { slug, title, mainImage, publishedAt, categories, readingTime }, - idx - ) => ( - -
- -
- -

- {title} -

- - - - - - - {Intl.DateTimeFormat('zh').format(new Date(publishedAt))} - - - - - - {categories.join(', ')} - - - - - - {prettifyNumber(views[idx] ?? 0, true)} - - - - - {readingTime.toFixed(0)}分钟阅读 - - - -
- - ) - )} + {posts.map((post, idx) => ( + + ))} ) } diff --git a/components/links/RichLink.tsx b/components/links/RichLink.tsx index 4e815916..da93fba6 100644 --- a/components/links/RichLink.tsx +++ b/components/links/RichLink.tsx @@ -7,6 +7,8 @@ import React from 'react' import { ExternalLinkIcon } from '~/assets' +const hostsThatNeedInvertedFavicons = ['github.com'] + type RichLinkProps = LinkProps & React.ComponentPropsWithoutRef<'a'> & { children: React.ReactNode @@ -15,12 +17,10 @@ type RichLinkProps = LinkProps & } export const RichLink = React.forwardRef( ({ children, href, favicon = true, className, ...props }, ref) => { + const hrefHost = new URL(href).host const faviconUrl = React.useMemo( - () => - href.startsWith('http') - ? `/api/favicon?url=${new URL(href).host}` - : null, - [href] + () => (href.startsWith('http') ? `/api/favicon?url=${hrefHost}` : null), + [hrefHost] ) // if it's a relative link, use a fallback Link @@ -45,7 +45,12 @@ export const RichLink = React.forwardRef( {...props} > {favicon && faviconUrl && ( - + { + const { + subject = '测试主题', + body = `# 你好,世界 + - 你好 + - 世界 + `, + } = props + + return ( + + {subject} + + {body && ( +
+ {body} +
+ )} +
+ ) +} + +export default NewslettersTemplate diff --git a/next.config.mjs b/next.config.mjs index 2dc6d3b0..172640be 100644 --- a/next.config.mjs +++ b/next.config.mjs @@ -8,6 +8,10 @@ import { get } from '@vercel/edge-config' /** @type {import('next').NextConfig} */ const nextConfig = { + experimental: { + serverActions: true, + }, + images: { domains: ['cdn.sanity.io'], }, @@ -33,9 +37,9 @@ const nextConfig = { { source: '/rss.xml', destination: '/feed.xml', - } + }, ] - } + }, } export default nextConfig