Skip to content

Commit bbf3a97

Browse files
authored
Merge pull request #23 from cocdeshijie/dev
Tags Route and Last Edited Date
2 parents fb6f559 + dc14a33 commit bbf3a97

File tree

10 files changed

+131
-41
lines changed

10 files changed

+131
-41
lines changed

app/articles/[...slug]/page.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ const ArticlePage = ({ params }: ArticlePageProps) => {
6262
<div className={"min-h-screen backdrop-blur-3xl bg-slate-50/50 dark:bg-gray-800/70"}>
6363
<div className={"flex min-h-[200px] md:min-h-[300px] rounded-b-2xl overflow-hidden"}
6464
style={{backgroundImage: 'url(' + article.image +')', backgroundSize: 'cover'}}>
65-
<ArticlesPageHeading title={article.title} date={article.date} tags={article.tags}/>
65+
<ArticlesPageHeading article={article}/>
6666
</div>
6767
<div className={cn(
6868
"pb-8 mx-4 md:mx-auto lg:px-4",

app/tags/[slug]/page.tsx

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { allArticles } from "contentlayer/generated";
2+
import ArticleLoader from "@/components/article/ArticleLoader";
3+
import { blogConfig } from "@/config";
4+
5+
export const generateStaticParams = () => [...new Set(allArticles.map(article => article.tags).flat())].map(tag => ({ slug: tag }));
6+
7+
const TagsPage = ({ params }: { params: { slug: string } }) => {
8+
const articlesWithTag = allArticles.filter(article =>
9+
article.tags && article.tags.map(tag => tag.trim()).includes(params.slug.trim())
10+
);
11+
12+
13+
console.log("Current Slug:", params.slug);
14+
console.log("All Tags:", allArticles.map(article => article.tags));
15+
16+
return (
17+
<div className={"min-h-screen backdrop-blur-3xl bg-slate-50/50 dark:bg-gray-800/70"}>
18+
<div className={"mx-4 lg:mx-auto lg:w-full max-w-4xl"}>
19+
<h1 className={"font-heading text-center text-xl md:text-2 xl mt-40 mb-10"}>Articles with {params.slug} tag</h1>
20+
<ArticleLoader articles={articlesWithTag} articlesPerLoad={blogConfig.articles_per_load} />
21+
</div>
22+
</div>
23+
);
24+
}
25+
26+
27+
export default TagsPage;

app/tags/page.tsx

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { allArticles } from "contentlayer/generated";
2+
import { Tag } from "@/components/Tags";
3+
4+
export default function TagsPage() {
5+
const allTags = allArticles.map(article => article.tags).flat().filter(Boolean);
6+
const uniqueTags = [...new Set(allTags)] as string[];
7+
8+
return (
9+
<div className={"min-h-screen backdrop-blur-3xl bg-slate-50/50 dark:bg-gray-800/70"}>
10+
<div className={"mx-4 lg:mx-auto lg:w-full max-w-4xl"}>
11+
<h1 className={"font-heading text-center text-xl md:text-2 xl mt-40 mb-10"}>Tags</h1>
12+
{uniqueTags.map(tag => (
13+
<Tag key={tag} tag={tag} />
14+
))}
15+
</div>
16+
</div>
17+
)
18+
}

components/Tags.tsx

Lines changed: 54 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,55 @@
11
import { cn } from "@/lib/utils";
2-
import { ReactNode } from "react";
3-
import { format, parseISO } from "date-fns";
2+
import { parseISO, differenceInDays, format } from 'date-fns';
3+
import * as Tooltip from "@radix-ui/react-tooltip";
4+
import { HiHashtag } from "react-icons/hi";
5+
import Link from "next/link";
46

57
type DateTagProps = {
68
date: string;
9+
lastEdited: string;
710
}
811

9-
export function DateTag({ date }: DateTagProps) {
12+
export function DateTag({ date, lastEdited }: DateTagProps) {
13+
const parsedDate = parseISO(date);
14+
const parsedLastEdited = parseISO(lastEdited);
15+
const daysDifference = differenceInDays(parsedLastEdited, parsedDate);
16+
17+
if (daysDifference > 1) {
18+
return (
19+
<Tooltip.Provider>
20+
<Tooltip.Root>
21+
<Tooltip.Trigger asChild>
22+
<div className={"bg-accent_color/50 dark:bg-accent_color-dark/70 rounded-md"}>
23+
<div className={"text-xs py-1 px-2"} suppressHydrationWarning={true}>
24+
Posted on {format(parsedDate, 'LLLL d, yyyy')} (Edited)
25+
</div>
26+
</div>
27+
</Tooltip.Trigger>
28+
<Tooltip.Portal>
29+
<Tooltip.Content
30+
className="bg-accent_color/50 dark:bg-accent_color-dark/70 rounded-md"
31+
sideOffset={5}
32+
suppressHydrationWarning={true}
33+
side={"right"}
34+
>
35+
<div className={"text-xs py-1 px-2"} suppressHydrationWarning={true}>
36+
Last edited on {format(parsedLastEdited, 'LLLL d, yyyy')}
37+
</div>
38+
<Tooltip.Arrow className="fill-accent_color/50 dark:fill-accent_color-dark/70" />
39+
</Tooltip.Content>
40+
</Tooltip.Portal>
41+
</Tooltip.Root>
42+
</Tooltip.Provider>
43+
)
44+
}
45+
1046
return (
1147
<div className={"bg-accent_color/50 dark:bg-accent_color-dark/70 rounded-md"}>
1248
<div className={"text-xs py-1 px-2"} suppressHydrationWarning={true}>
13-
Posted on {format(parseISO(date), 'LLLL d, yyyy')}
49+
Posted on {format(parsedDate, 'LLLL d, yyyy')}
1450
</div>
1551
</div>
16-
)
52+
);
1753
}
1854

1955
export function NoTag() {
@@ -28,16 +64,20 @@ export function NoTag() {
2864
}
2965

3066
type TagProps = {
31-
children: ReactNode;
67+
tag: string;
3268
};
3369

34-
export function Tag({ children }: TagProps) {
70+
export function Tag({ tag }: TagProps) {
3571
return (
36-
<div className={cn(
37-
"inline-flex text-xs rounded-md mr-2 items-center",
38-
"bg-secondary_color/50 dark:bg-secondary_color-dark/70"
39-
)}>
40-
{children}
41-
</div>
42-
)
72+
<Link href={`/tags/${tag}`}>
73+
<div className={cn(
74+
"inline-flex text-xs rounded-md mr-2 items-center",
75+
"bg-secondary_color/50 dark:bg-secondary_color-dark/70",
76+
"hover:scale-125 duration-500"
77+
)}>
78+
<HiHashtag className={"w-3 h-3 ml-1"}/>
79+
<div className={"text-xs py-1 px-1"}>{tag}</div>
80+
</div>
81+
</Link>
82+
);
4383
}

components/article/ArticleCard.tsx

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import Image from "next/image";
44
import Link from "next/link";
55
import { Article } from "contentlayer/generated";
66
import { cn } from "@/lib/utils";
7-
import { HiHashtag } from "react-icons/hi";
87
import { useState } from "react";
98
import { DateTag, NoTag, Tag } from "@/components/Tags";
109

@@ -46,7 +45,7 @@ export default function ArticleCard({ article, idx }: ArticleCardProps) {
4645

4746
<div className="w-full md:w-5/12 p-4 lg:px-8">
4847
<div className={"flex mb-2"}>
49-
<DateTag date={article.date}></DateTag>
48+
<DateTag date={article.date} lastEdited={article.lastEdited}/>
5049
</div>
5150
<div className={"flex mb-3"}>
5251
{(() => {
@@ -56,10 +55,7 @@ export default function ArticleCard({ article, idx }: ArticleCardProps) {
5655
)
5756
} else {
5857
return article.tags.map((tag) => (
59-
<Tag key={tag}>
60-
<HiHashtag className={"w-3 h-3 ml-1"}/>
61-
<div className={"text-xs py-1 px-1"}>{tag}</div>
62-
</Tag>
58+
<Tag key={tag} tag={tag}/>
6359
))
6460
}
6561
})()}

components/article/ArticlesPageHeading.tsx

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
11
"use client";
22

33
import { motion } from "framer-motion";
4-
import { HiHashtag } from "react-icons/hi";
54
import { DateTag, NoTag, Tag } from "@/components/Tags";
5+
import { type Article } from "contentlayer/generated";
66

7-
interface ArticlePageHeadingProps {
8-
title: string;
9-
date: string;
10-
tags?: string[];
7+
interface ArticlesPageHeadingProps {
8+
article: Article; // Assuming you have a type named 'Article'
119
}
1210

13-
export default function ArticlesPageHeading({ title, date, tags }: ArticlePageHeadingProps) {
11+
export default function ArticlesPageHeading({ article }: ArticlesPageHeadingProps) {
12+
const { title, date, lastEdited, tags } = article;
13+
1414
let FADE_DOWN_ANIMATION_VARIANTS = {
1515
hidden: { opacity: 0, y: -10 },
1616
show: { opacity: 1, y: 0, transition: { type: "spring" } },
@@ -54,7 +54,7 @@ export default function ArticlesPageHeading({ title, date, tags }: ArticlePageHe
5454
className={"flex mb-2"}
5555
variants={FADE_DOWN_ANIMATION_VARIANTS}
5656
>
57-
<DateTag date={date}/>
57+
<DateTag date={date} lastEdited={lastEdited}/>
5858
</motion.div>
5959
<motion.div
6060
className={"flex"}
@@ -68,10 +68,7 @@ export default function ArticlesPageHeading({ title, date, tags }: ArticlePageHe
6868
)
6969
} else {
7070
return tags.map((tag) => (
71-
<Tag key={tag}>
72-
<HiHashtag className={"w-3 h-3 ml-1"}/>
73-
<div className={"text-xs py-1 px-1"}>{tag}</div>
74-
</Tag>
71+
<Tag key={tag} tag={tag}/>
7572
))
7673
}
7774
})()}

components/search/SearchResults.tsx

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
import { type Article } from "contentlayer/generated";
22
import { allArticles } from "contentlayer/generated";
3-
import {cn} from "@/lib/utils";
3+
import { cn } from "@/lib/utils";
44
import Link from "next/link";
55
import { useSearchStore } from '@/stores/search-store';
6-
import { HiHashtag } from "react-icons/hi";
76
import { DateTag, NoTag, Tag } from "@/components/Tags";
87

98
type ArticleCardProps = {
@@ -29,18 +28,15 @@ function ArticleCard({ article }: ArticleCardProps) {
2928
{article.excerpt}
3029
</p>
3130
<div className={"flex space-x-1"}>
32-
<DateTag date={article.date} />
31+
<DateTag date={article.date} lastEdited={article.lastEdited}/>
3332
{(() => {
3433
if (!article.tags || article.tags.length === 0) {
3534
return (
3635
<NoTag />
3736
)
3837
} else {
3938
return article.tags.map((tag) => (
40-
<Tag key={tag}>
41-
<HiHashtag className={"w-3 h-3 ml-1"}/>
42-
<div className={"text-xs py-1 px-1"}>{tag}</div>
43-
</Tag>
39+
<Tag key={tag} tag={tag}/>
4440
))
4541
}
4642
})()}

contentlayer.config.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { remarkCodeHike } from "@code-hike/mdx";
33
import remarkGfm from "remark-gfm";
44
import rehypeSlug from "rehype-slug";
55
import rehypeAutolinkHeadings, { type Options as AutolinkOptions } from 'rehype-autolink-headings';
6+
import { execSync } from "child_process";
67

78
const computedFields: ComputedFields = {
89
slug: {
@@ -31,6 +32,20 @@ export const Article = defineDocumentType(() => ({
3132
type: 'string',
3233
resolve: (article) => article.image = article.image + "?r=" + Math.random(),
3334
},
35+
lastEdited: {
36+
type: 'string',
37+
resolve: (article) => {
38+
try {
39+
const filePath = `./content/${article._raw.flattenedPath}.mdx`;
40+
const dateStr = execSync(`git log -1 --format="%ad" -- ${filePath}`, {
41+
encoding: 'utf-8'
42+
});
43+
return new Date(dateStr).toISOString();
44+
} catch (e) {
45+
return new Date(article.date).toISOString();
46+
}
47+
}
48+
},
3449
...computedFields,
3550
},
3651
}))

lib/utils.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import { clsx, type ClassValue } from 'clsx';
2-
import { twMerge } from 'tailwind-merge';
1+
import { type ClassValue, clsx } from "clsx";
2+
import { twMerge } from "tailwind-merge";
33

44
/** Utility function to merge Tailwind classes with clsx and tailwind-merge */
55
export function cn(...inputs: ClassValue[]) {

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
"@radix-ui/react-dialog": "^1.0.4",
3030
"@radix-ui/react-dropdown-menu": "^2.0.5",
3131
"@radix-ui/react-navigation-menu": "^1.1.3",
32+
"@radix-ui/react-tooltip": "^1.0.6",
3233
"@tailwindcss/typography": "^0.5.9",
3334
"@types/mdx": "^2.0.5",
3435
"@types/node": "20.2.5",

0 commit comments

Comments
 (0)