Skip to content

Commit 6558192

Browse files
authored
Merge pull request #90 from techdiary-dev/kingrayhan/fix
Kingrayhan/fix
2 parents 62c0159 + db5b13f commit 6558192

18 files changed

Lines changed: 763 additions & 152 deletions

File tree

CHANGELOG.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,22 @@ This project adheres to [Semantic Versioning](https://semver.org/).
66

77
---
88

9+
## v1.4.0 — 2026-03-30
10+
11+
### ✨ Features
12+
- feat: extend resource type support to include GIST across comments and reactions (e4e6a86)
13+
- feat: enhance CommentSection with loading states and improved layout (eaf6639)
14+
- feat: implement resolveArticleExcerpt utility for improved excerpt handling (bccf312)
15+
16+
### 🐛 Bug Fixes
17+
- fix: enhance comment functionality with update and delete actions (c80f529)
18+
19+
### 🔧 Other Changes
20+
- refactor: improve comment item layout and transition effects (9268b61)
21+
- refactor: adjust layout and styling in ArticleCard and UserInformationCard components (a9caafc)
22+
23+
---
24+
925
## v1.3.0 — 2026-03-30
1026

1127
### ✨ Features

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "techdiary.dev-next",
3-
"version": "1.3.0",
3+
"version": "1.4.0",
44
"private": true,
55
"scripts": {
66
"dev": "next dev --turbo",

src/app/(home)/_components/HomeLeftSidebar.tsx

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
UserIcon,
1616
HashIcon,
1717
CodeIcon,
18+
TagsIcon,
1819
} from "lucide-react";
1920
import Link from "next/link";
2021

@@ -47,7 +48,10 @@ const Sidebar = () => {
4748
staleTime: 1000 * 60 * 5,
4849
});
4950

50-
const tags = tagsQuery.data?.success ? tagsQuery.data.data : [];
51+
const tags = (tagsQuery.data?.success ? tagsQuery.data.data : []) as {
52+
id: string;
53+
name: string;
54+
}[];
5155

5256
return (
5357
<div className="flex flex-col gap-6 pt-4">
@@ -103,7 +107,12 @@ const Sidebar = () => {
103107
<p className="px-2 mb-1 text-xs font-semibold uppercase tracking-wide text-muted-foreground">
104108
{_t("Topics")}
105109
</p>
106-
{tags.map((tag: any) => (
110+
<NavLink
111+
href="/tags"
112+
icon={<TagsIcon size={17} />}
113+
label={_t("All tags")}
114+
/>
115+
{tags.map((tag) => (
107116
<NavLink
108117
key={tag.id}
109118
href={`/tags/${tag.name}`}

src/app/[username]/[articleHandle]/page.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ interface ArticlePageProps {
3333
}
3434

3535
export async function generateMetadata(
36-
options: ArticlePageProps
36+
options: ArticlePageProps,
3737
): Promise<Metadata> {
3838
// read route params
3939
const { articleHandle } = await options.params;
@@ -57,7 +57,7 @@ export async function generateMetadata(
5757

5858
const description = removeMarkdownSyntax(
5959
article.excerpt ?? article.body ?? "",
60-
20
60+
20,
6161
);
6262

6363
const openGraph: OpenGraph = {
@@ -89,7 +89,7 @@ export async function generateMetadata(
8989
const Page: NextPage<ArticlePageProps> = async ({ params }) => {
9090
const _params = await params;
9191
const article = await articleActions.articleDetailByHandle(
92-
_params.articleHandle
92+
_params.articleHandle,
9393
);
9494

9595
const jsonLd: WithContext<Article> = {
@@ -180,7 +180,7 @@ const Page: NextPage<ArticlePageProps> = async ({ params }) => {
180180
month: "long",
181181
day: "numeric",
182182
year: "numeric",
183-
}
183+
},
184184
)}
185185
</time>
186186
<span className="mx-1.5">·</span>
@@ -230,7 +230,7 @@ const Page: NextPage<ArticlePageProps> = async ({ params }) => {
230230
</div>
231231

232232
<div className="mx-auto content-typography">
233-
{article.body && <Markdown content={article.body!} />}
233+
{article?.body && <Markdown content={article?.body!} />}
234234
</div>
235235
</div>
236236

src/app/tags/page.tsx

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import HomeLeftSidebar from "@/app/(home)/_components/HomeLeftSidebar";
2+
import HomeRightSidebar from "@/app/(home)/_components/HomeRightSidebar";
3+
import SidebarToggleButton from "@/app/(home)/_components/SidebarToggleButton";
4+
import { getTagsWithArticleCounts } from "@/backend/services/tag.action";
5+
import HomepageLayout from "@/components/layout/HomepageLayout";
6+
import Link from "next/link";
7+
8+
export default async function TagsIndexPage() {
9+
const result = await getTagsWithArticleCounts();
10+
11+
const tags = result.success ? result.data : [];
12+
13+
return (
14+
<HomepageLayout
15+
LeftSidebar={<HomeLeftSidebar />}
16+
RightSidebar={<HomeRightSidebar />}
17+
NavbarTrailing={<SidebarToggleButton />}
18+
>
19+
<div className="px-4 py-6 md:py-8 w-full max-w-4xl mx-auto">
20+
<header className="mb-8">
21+
<h1 className="text-2xl font-bold tracking-tight text-foreground">
22+
Tags
23+
</h1>
24+
<p className="mt-1.5 text-sm text-muted-foreground">
25+
Tags with the most articles first. Counts include published articles only.
26+
</p>
27+
</header>
28+
29+
{!result.success && (
30+
<p className="text-sm text-destructive" role="alert">
31+
{result.error}
32+
</p>
33+
)}
34+
35+
{result.success && tags.length === 0 && (
36+
<p className="text-sm text-muted-foreground">No tags yet.</p>
37+
)}
38+
39+
{tags.length > 0 && (
40+
<ul className="grid gap-3 sm:grid-cols-2">
41+
{tags.map((tag) => (
42+
<li key={tag.id}>
43+
<Link
44+
href={`/tags/${encodeURIComponent(tag.name)}`}
45+
className="flex items-center justify-between gap-3 rounded-lg border border-border bg-card px-4 py-3 text-left transition-colors hover:bg-muted/60"
46+
>
47+
<span className="font-medium text-foreground truncate">
48+
#{tag.name}
49+
</span>
50+
<span className="shrink-0 text-xs tabular-nums text-muted-foreground">
51+
{tag.article_count}{" "}
52+
{tag.article_count === 1 ? "article" : "articles"}
53+
</span>
54+
</Link>
55+
</li>
56+
))}
57+
</ul>
58+
)}
59+
</div>
60+
</HomepageLayout>
61+
);
62+
}
63+
64+
export async function generateMetadata() {
65+
return {
66+
title: "Tags — TechDiary",
67+
description:
68+
"Browse all topics on TechDiary with article counts for each tag.",
69+
openGraph: {
70+
title: "Tags — TechDiary",
71+
description:
72+
"Browse all topics on TechDiary with article counts for each tag.",
73+
},
74+
};
75+
}

src/backend/models/domain-models.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -156,10 +156,11 @@ export interface BookmarkArticlePresentation {
156156
export interface Comment {
157157
id: string;
158158
resource_id: string;
159-
resource_type: "ARTICLE" | "COMMENT";
159+
resource_type: "ARTICLE" | "COMMENT" | "GIST";
160160
body?: string;
161161
user_id: string;
162162
created_at: Date;
163+
updated_at?: Date;
163164
}
164165

165166
export interface CommentPresentation {
@@ -195,7 +196,7 @@ export enum DIRECTORY_NAME {
195196

196197
export interface Reaction {
197198
resource_id: string;
198-
resource_type: "ARTICLE" | "COMMENT";
199+
resource_type: "ARTICLE" | "COMMENT" | "GIST";
199200
reaction_type: REACTION_TYPE;
200201
user_id: string;
201202
created_at: Date;

src/backend/services/article.actions.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,12 @@
33
import { cacheTag, revalidateTag } from "next/cache";
44
import { pgClient } from "@/backend/persistence/clients";
55
import { slugify } from "@/lib/slug-helper.util";
6-
import { removeMarkdownSyntax, removeUndefinedFromObject, generateRandomString } from "@/lib/utils";
6+
import {
7+
removeMarkdownSyntax,
8+
removeUndefinedFromObject,
9+
generateRandomString,
10+
resolveArticleExcerpt,
11+
} from "@/lib/utils";
712
import { addDays } from "date-fns";
813
import * as sk from "sqlkit";
914
import { and, desc, eq, like, neq, or } from "sqlkit";
@@ -299,6 +304,7 @@ export async function deleteArticle(article_id: string) {
299304
where: eq("id", article_id),
300305
});
301306

307+
revalidateTag("tags-list", "max");
302308
return deletedArticles?.rows?.[0];
303309
} catch (error) {
304310
handleActionException(error);
@@ -427,7 +433,7 @@ export async function userArticleFeed(
427433
response["nodes"] = response["nodes"].map((article) => {
428434
return {
429435
...article,
430-
excerpt: article.excerpt ?? removeMarkdownSyntax(article.body),
436+
excerpt: resolveArticleExcerpt(article.excerpt, article.body),
431437
};
432438
});
433439

@@ -498,7 +504,7 @@ export async function articlesByTag(
498504
cover_image: row.cover_image,
499505
body: row.body,
500506
created_at: new Date(row.created_at),
501-
excerpt: row.excerpt ?? removeMarkdownSyntax(row.body),
507+
excerpt: resolveArticleExcerpt(row.excerpt, row.body),
502508
user: {
503509
id: row.user_id,
504510
name: row.user_name,
@@ -702,6 +708,7 @@ export async function setArticlePublished(
702708
if (articles?.rows?.[0] && !is_published) {
703709
deleteArticleById(article_id);
704710
}
711+
revalidateTag("tags-list", "max");
705712
return articles?.rows?.[0];
706713
} catch (error) {
707714
return handleActionException(error);

0 commit comments

Comments
 (0)