mirror of
https://github.com/10h30/blog-balodeplao.git
synced 2026-05-12 15:21:15 +09:00
feat: enhance blog structure with categories, tags, and localization updates
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
---
|
||||
import Tags from "@/components/ui/Tags.astro";
|
||||
import Categories from "@/components/ui/Categories.astro";
|
||||
import { type CollectionEntry, render } from "astro:content";
|
||||
|
||||
export interface Props {
|
||||
@@ -10,7 +11,7 @@ const { post } = Astro.props;
|
||||
const { remarkPluginFrontmatter } = await render(post);
|
||||
|
||||
const readingTime = remarkPluginFrontmatter?.minutesRead
|
||||
? `${Math.ceil(remarkPluginFrontmatter.minutesRead)} min read`
|
||||
? `${Math.ceil(remarkPluginFrontmatter.minutesRead)} phút đọc`
|
||||
: "";
|
||||
---
|
||||
|
||||
@@ -34,7 +35,7 @@ const readingTime = remarkPluginFrontmatter?.minutesRead
|
||||
{post.data.author}
|
||||
</p>
|
||||
<a
|
||||
href={`/blog/${post.id}`}
|
||||
href={`/${post.id}`}
|
||||
class="mt-2 block before:absolute before:inset-0 before:z-10"
|
||||
>
|
||||
<p class="text-xl font-semibold text-foreground group-hover:underline">
|
||||
@@ -55,7 +56,7 @@ const readingTime = remarkPluginFrontmatter?.minutesRead
|
||||
<div class="flex space-x-1 text-sm text-muted-foreground">
|
||||
<time datetime={post.data.pubDate.toISOString()}>
|
||||
{
|
||||
post.data.pubDate.toLocaleDateString("en-US", {
|
||||
post.data.pubDate.toLocaleDateString("vi-VN", {
|
||||
year: "numeric",
|
||||
month: "long",
|
||||
day: "numeric",
|
||||
@@ -69,6 +70,14 @@ const readingTime = remarkPluginFrontmatter?.minutesRead
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{
|
||||
post.data.categories && (
|
||||
<div class="mt-4 relative z-20 flex flex-wrap gap-2">
|
||||
<Categories categories={post.data.categories} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
{
|
||||
post.data.tags && (
|
||||
<div class="mt-4 relative z-20">
|
||||
|
||||
@@ -13,25 +13,18 @@ import { siteConfig } from "@/config/site";
|
||||
</div>
|
||||
<div class="flex items-center gap-6">
|
||||
<a
|
||||
href={siteConfig.socialLinks.twitter}
|
||||
href={siteConfig.socialLinks.instagram}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="text-muted-foreground transition-colors hover:text-primary"
|
||||
>Twitter</a
|
||||
>Instagram</a
|
||||
>
|
||||
<a
|
||||
href={siteConfig.socialLinks.github}
|
||||
href={siteConfig.socialLinks.facebook}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="text-muted-foreground transition-colors hover:text-primary"
|
||||
>GitHub</a
|
||||
>
|
||||
<a
|
||||
href={siteConfig.socialLinks.discord}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="text-muted-foreground transition-colors hover:text-primary"
|
||||
>Discord</a
|
||||
>Facebook</a
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
@@ -39,7 +32,7 @@ import { siteConfig } from "@/config/site";
|
||||
class="mt-8 border-t border-border pt-8 text-center text-xs text-muted-foreground"
|
||||
>
|
||||
© {new Date().getFullYear()}
|
||||
{siteConfig.author}. All rights reserved.
|
||||
{siteConfig.name}. All rights reserved.
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
@@ -28,7 +28,7 @@ if (type === "WebSite") {
|
||||
"@type": "Person",
|
||||
name: siteConfig.author,
|
||||
},
|
||||
sameAs: [siteConfig.socialLinks.twitter, siteConfig.socialLinks.github],
|
||||
sameAs: [siteConfig.socialLinks.instagram, siteConfig.socialLinks.facebook],
|
||||
};
|
||||
} else if (type === "BlogPosting") {
|
||||
schema = {
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
---
|
||||
export interface Props {
|
||||
categories: string[];
|
||||
class?: string;
|
||||
}
|
||||
|
||||
const { categories, class: className = "text-sm" } = Astro.props;
|
||||
---
|
||||
|
||||
{
|
||||
categories && Array.isArray(categories) && (
|
||||
<ul class:list={["flex flex-wrap gap-2", className]}>
|
||||
{categories.map((category) => (
|
||||
<li class="inline-block relative">
|
||||
<a
|
||||
href={`/category/${category}`}
|
||||
class="inline-block bg-primary/10 hover:bg-primary/20 text-primary hover:text-primary px-3 py-1 rounded-full border border-primary/20 transition-colors duration-200"
|
||||
>
|
||||
{category}
|
||||
</a>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)
|
||||
}
|
||||
@@ -28,7 +28,7 @@ const { prev, next } = url;
|
||||
<span />
|
||||
)}
|
||||
<div class="flex items-center text-sm text-muted-foreground">
|
||||
Página {currentPage} de {lastPage}
|
||||
Trang {currentPage} / {lastPage}
|
||||
</div>
|
||||
{next ? (
|
||||
<a
|
||||
|
||||
@@ -13,7 +13,7 @@ const { tags, class: className = "text-sm" } = Astro.props;
|
||||
{tags.map((tag) => (
|
||||
<li class="inline-block relative">
|
||||
<a
|
||||
href={`/blog/tags/${tag}`}
|
||||
href={`/tags/${tag}`}
|
||||
class="inline-block bg-muted/50 hover:bg-muted text-muted-foreground hover:text-foreground px-3 py-1 rounded-full border border-border transition-colors duration-200"
|
||||
>
|
||||
#{tag}
|
||||
|
||||
+8
-12
@@ -2,25 +2,21 @@ import ogImage from "@/assets/og-image.png";
|
||||
|
||||
export const siteConfig = {
|
||||
name: "Astro Starter Pro",
|
||||
description:
|
||||
"Starter template optimized for SEO and performance. A solid foundation to start your projects with best practices.",
|
||||
url: "https://astrostarterpro.com",
|
||||
lang: "en",
|
||||
locale: "en_US",
|
||||
author: "Devgelo",
|
||||
twitter: "@Devgelo",
|
||||
description: "Just another Astro blog",
|
||||
url: "https://thuanbui.me",
|
||||
lang: "vi",
|
||||
locale: "vi_VN",
|
||||
author: "Thuan Bui",
|
||||
twitter: "@10h30",
|
||||
ogImage: ogImage,
|
||||
socialLinks: {
|
||||
twitter: "https://twitter.com",
|
||||
github: "https://github.com/devgelo-labs/astro-starter-pro",
|
||||
discord: "https://discord.com",
|
||||
instagram: "https://instagram.com/",
|
||||
facebook: "https://facebook.com/",
|
||||
},
|
||||
navLinks: [
|
||||
{ text: "Home", href: "/" },
|
||||
{ text: "About", href: "/about" },
|
||||
{ text: "Services", href: "/services" },
|
||||
{ text: "Blog", href: "/blog" },
|
||||
{ text: "Contact", href: "/contact" },
|
||||
{ text: "Widgets", href: "/widgets" },
|
||||
],
|
||||
};
|
||||
|
||||
@@ -5,12 +5,12 @@ const blog = defineCollection({
|
||||
loader: glob({ pattern: "**/*.{md,mdx}", base: "./src/content/blog" }),
|
||||
schema: z.object({
|
||||
title: z.string(),
|
||||
description: z.string(),
|
||||
description: z.string().optional(),
|
||||
pubDate: z.coerce.date(),
|
||||
author: z.string(),
|
||||
image: z.string().optional(),
|
||||
tags: z.array(z.string()).optional(),
|
||||
category: z.string().optional(),
|
||||
categories: z.array(z.string()).optional(),
|
||||
}),
|
||||
});
|
||||
|
||||
|
||||
@@ -16,14 +16,14 @@ const { post } = Astro.props;
|
||||
const { Content, remarkPluginFrontmatter } = await render(post);
|
||||
const { title, description, pubDate, author, image } = post.data;
|
||||
|
||||
const formattedDate = pubDate.toLocaleDateString("es-ES", {
|
||||
const formattedDate = pubDate.toLocaleDateString("vi-VN", {
|
||||
year: "numeric",
|
||||
month: "long",
|
||||
day: "numeric",
|
||||
});
|
||||
|
||||
const readingTime = remarkPluginFrontmatter?.minutesRead
|
||||
? `${Math.ceil(remarkPluginFrontmatter.minutesRead)} min de lectura`
|
||||
? `${Math.ceil(remarkPluginFrontmatter.minutesRead)} phút đọc`
|
||||
: "";
|
||||
|
||||
const metadata = {
|
||||
@@ -62,7 +62,11 @@ const metadata = {
|
||||
{
|
||||
image && (
|
||||
<div class="reveal relative mb-8 h-96 w-full overflow-hidden rounded-xl shadow-lg">
|
||||
<img src={image} alt={title} class="h-full w-full object-cover" />
|
||||
<img
|
||||
src={`/images/${image}`}
|
||||
alt={title}
|
||||
class="h-full w-full object-cover"
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -10,9 +10,7 @@ export async function getStaticPaths() {
|
||||
|
||||
const categories = new Set();
|
||||
posts.forEach((post) => {
|
||||
if (post.data.category) {
|
||||
categories.add(post.data.category);
|
||||
}
|
||||
post.data.categories?.forEach((tag) => categories.add(tag));
|
||||
});
|
||||
|
||||
return Array.from(categories).map((category) => {
|
||||
@@ -20,7 +18,7 @@ export async function getStaticPaths() {
|
||||
params: { category: category as string },
|
||||
props: {
|
||||
category,
|
||||
posts: posts.filter((post) => post.data.category === category),
|
||||
posts: posts.filter((post) => post.data.categories === category),
|
||||
},
|
||||
};
|
||||
});
|
||||
@@ -50,7 +48,7 @@ const metadata = {
|
||||
/>
|
||||
|
||||
<div class="mb-8 text-center">
|
||||
<a href="/blog" class="text-primary hover:underline">← Back to blog</a>
|
||||
<a href="/blog" class="text-primary hover:underline">← Quay lại blog</a>
|
||||
</div>
|
||||
|
||||
<div class="grid gap-8 md:grid-cols-2 lg:grid-cols-3">
|
||||
@@ -48,7 +48,7 @@ const metadata = {
|
||||
/>
|
||||
|
||||
<div class="mb-8 text-center">
|
||||
<a href="/blog" class="text-primary hover:underline">← Back to blog</a>
|
||||
<a href="/blog" class="text-primary hover:underline">← Quay lại blog</a>
|
||||
</div>
|
||||
|
||||
<div class="grid gap-8 md:grid-cols-2 lg:grid-cols-3">
|
||||
Reference in New Issue
Block a user