From ff58dd000c50b470c5881380f8c12fb5e6e6e1e7 Mon Sep 17 00:00:00 2001 From: kbot Date: Mon, 6 Jul 2026 20:15:54 -0500 Subject: [PATCH] refactor(tags): centralize tag slug logic in client-safe util --- app/posts/[slug]/page.tsx | 3 ++- components/posts/PostCard.tsx | 15 ++------------- components/posts/PostSearch.tsx | 14 +++----------- lib/posts.ts | 15 +++------------ lib/tags.ts | 11 +++++++++++ 5 files changed, 21 insertions(+), 37 deletions(-) create mode 100644 lib/tags.ts diff --git a/app/posts/[slug]/page.tsx b/app/posts/[slug]/page.tsx index 4762c1b..e58d846 100644 --- a/app/posts/[slug]/page.tsx +++ b/app/posts/[slug]/page.tsx @@ -1,6 +1,7 @@ import { notFound } from 'next/navigation' import Link from 'next/link' -import { getPosts, getPost, tagToSlug } from '@/lib/posts' +import { getPosts, getPost } from '@/lib/posts' +import { tagToSlug } from '@/lib/tags' import { TableOfContents } from '@/components/posts/TableOfContents' import { ScrollToTop } from '@/components/ui/ScrollToTop' import { ReadingProgress } from '@/components/ui/ReadingProgress' diff --git a/components/posts/PostCard.tsx b/components/posts/PostCard.tsx index 1f158a8..136e430 100644 --- a/components/posts/PostCard.tsx +++ b/components/posts/PostCard.tsx @@ -3,18 +3,7 @@ import Link from 'next/link'; import { m, useReducedMotion } from 'motion/react'; import type { PostMeta } from '@/lib/posts'; - -const tagToClientSlug = (tag: string): string => - tag - .normalize('NFKD') - .toLowerCase() - .replace(/[\u0300-\u036f]/g, '') - .replace(/\+/g, ' plus ') - .replace(/#/g, ' sharp ') - .replace(/[\\/]+/g, ' ') - .replace(/[^\p{L}\p{N}]+/gu, '-') - .replace(/-{2,}/g, '-') - .replace(/^-|-$/g, ''); +import { tagToSlug } from '@/lib/tags'; export function PostCard({ slug, title, date, excerpt, tags = [], author, readingTime, index = 0, coverImage }: PostMeta & { index?: number }) { const shouldReduceMotion = useReducedMotion(); @@ -56,7 +45,7 @@ export function PostCard({ slug, title, date, excerpt, tags = [], author, readin {tags && tags.length > 0 && (
{tags.slice(0, 3).map((tag) => ( - + {tag} ))} diff --git a/components/posts/PostSearch.tsx b/components/posts/PostSearch.tsx index 0ddf554..de24deb 100644 --- a/components/posts/PostSearch.tsx +++ b/components/posts/PostSearch.tsx @@ -1,6 +1,7 @@ 'use client' import { useMemo, useState } from 'react' +import { tagToSlug } from '@/lib/tags' import { PostCard } from './PostCard' type SearchPost = { @@ -37,15 +38,6 @@ const normalizeSearchText = (value: string): string => .toLowerCase() .trim() -const tagToClientSlug = (tag: string): string => - normalizeSearchText(tag) - .replace(/\+/g, ' plus ') - .replace(/#/g, ' sharp ') - .replace(/[\\/]+/g, ' ') - .replace(/[^\p{L}\p{N}]+/gu, '-') - .replace(/-{2,}/g, '-') - .replace(/^-|-$/g, '') - const getOrderedCharacterScore = (field: string, query: string): number => { if (!query) return 0 @@ -100,7 +92,7 @@ const getTagFilters = (posts: SearchPost[]): TagFilter[] => { const postTags = new Set() post.tags.forEach((tag) => { - const slug = tagToClientSlug(tag) + const slug = tagToSlug(tag) if (!slug || postTags.has(slug)) return postTags.add(slug) @@ -130,7 +122,7 @@ export function PostSearch({ posts }: PostSearchProps) { const results = useMemo(() => { const scoredPosts = posts.reduce((matches, post, index) => { - if (selectedTag && !post.tags.some((tag) => tagToClientSlug(tag) === selectedTag)) { + if (selectedTag && !post.tags.some((tag) => tagToSlug(tag) === selectedTag)) { return matches } diff --git a/lib/posts.ts b/lib/posts.ts index 273657f..1236052 100644 --- a/lib/posts.ts +++ b/lib/posts.ts @@ -2,6 +2,9 @@ import fs from 'fs' import path from 'path' import matter from 'gray-matter' import { cache } from 'react' +import { tagToSlug } from '@/lib/tags' + +export { tagToSlug } from '@/lib/tags' const postsDirectory = path.join(process.cwd(), 'content/posts') @@ -61,18 +64,6 @@ export const normalizePostData = ( readingTime, }) -export const tagToSlug = (tag: string): string => - tag - .normalize('NFKD') - .toLowerCase() - .replace(/[\u0300-\u036f]/g, '') - .replace(/\+/g, ' plus ') - .replace(/#/g, ' sharp ') - .replace(/[\\/]+/g, ' ') - .replace(/[^\p{L}\p{N}]+/gu, '-') - .replace(/-{2,}/g, '-') - .replace(/^-|-$/g, '') - const getMdxFiles = cache(async () => { try { const files = await fs.promises.readdir(postsDirectory) diff --git a/lib/tags.ts b/lib/tags.ts new file mode 100644 index 0000000..55f4ab0 --- /dev/null +++ b/lib/tags.ts @@ -0,0 +1,11 @@ +export const tagToSlug = (tag: string): string => + tag + .normalize('NFKD') + .toLowerCase() + .replace(/[\u0300-\u036f]/g, '') + .replace(/\+/g, ' plus ') + .replace(/#/g, ' sharp ') + .replace(/[\\/]+/g, ' ') + .replace(/[^\p{L}\p{N}]+/gu, '-') + .replace(/-{2,}/g, '-') + .replace(/^-|-$/g, '')