Files
zen-blog/components/read-more-section.tsx
zeekay 712a10d0f3 Lead the blog with Zen 6 and its two jobs
The home now says who makes Zen LM, Zoo Labs Foundation, a 501(c)(3)
non-profit, the two jobs it is chosen for, that Zen 6 and Zen 6 Flash
are available now, and that Zen 7 is a research preview with a link
to request access. The footer names the foundation, and "Qwen" leaves
the site keywords.

Posts marked "upstream: true" carry data-upstream on their page, card
and read-more link, and scripts/check-zen.mjs fails the build when a
name from zen.upstream shows anywhere else.

The build stops measuring remote images: fumadocs fetched every one to
read its size, and a single host answering 403 failed the build.
2026-09-30 07:01:46 -07:00

122 lines
3.4 KiB
TypeScript

/* eslint-disable @next/next/no-img-element */
import { docs, meta } from "@/.source";
import { loader } from "fumadocs-core/source";
import { createMDXSource } from "fumadocs-mdx";
import Link from "next/link";
const blogSource = loader({
baseUrl: "/blog",
source: createMDXSource(docs, meta),
});
const formatDate = (date: Date): string => {
return date.toLocaleDateString("en-US", {
year: "numeric",
month: "long",
day: "numeric",
});
};
interface BlogData {
title: string;
description: string;
date: string;
tags?: string[];
featured?: boolean;
readTime?: string;
author?: string;
authorImage?: string;
thumbnail?: string;
upstream?: boolean;
}
interface BlogPage {
url: string;
data: BlogData;
}
interface ReadMoreSectionProps {
currentSlug: string[];
currentTags?: string[];
}
export function ReadMoreSection({
currentSlug,
currentTags = [],
}: ReadMoreSectionProps) {
const allPages = blogSource.getPages() as BlogPage[];
const currentUrl = `/blog/${currentSlug.join("/")}`;
const otherPosts = allPages
.filter((page) => page.url !== currentUrl)
.map((page) => {
const tagOverlap = currentTags.filter((tag) =>
page.data.tags?.includes(tag)
).length;
return {
...page,
relevanceScore: tagOverlap,
date: new Date(page.data.date),
};
})
.sort((a, b) => {
if (a.relevanceScore !== b.relevanceScore) {
return b.relevanceScore - a.relevanceScore;
}
return b.date.getTime() - a.date.getTime();
})
.slice(0, 3);
if (otherPosts.length === 0) {
return null;
}
return (
<section className="border-t border-border p-0">
<div className="p-6 lg:p-10">
<h2 className="text-2xl font-medium mb-8">Read more</h2>
<div className="flex flex-col gap-8">
{otherPosts.map((post) => {
const formattedDate = formatDate(post.date);
return (
<Link
key={post.url}
href={post.url}
data-upstream={post.data.upstream ? "" : undefined}
className="group grid grid-cols-1 lg:grid-cols-12 items-center gap-4 cursor-pointer"
>
{post.data.thumbnail && (
<div className="flex-shrink-0 col-span-1 lg:col-span-4">
<div className="relative w-full h-full">
<img
src={post.data.thumbnail}
alt={post.data.title}
className="w-full h-full object-cover rounded-lg group-hover:opacity-80 transition-opacity"
/>
</div>
</div>
)}
<div className="space-y-2 flex-1 col-span-1 lg:col-span-8">
<h3 className="text-lg group-hover:underline underline-offset-4 font-semibold text-card-foreground group-hover:text-primary transition-colors line-clamp-2">
{post.data.title}
</h3>
<p className="text-muted-foreground text-sm line-clamp-3 group-hover:underline underline-offset-4">
{post.data.description}
</p>
<time className="block text-xs font-medium text-muted-foreground">
{formattedDate}
</time>
</div>
</Link>
);
})}
</div>
</div>
</section>
);
}