🏷️ added tags system

This commit is contained in:
RJ
2025-11-19 13:25:36 +02:00
parent ec37c33afa
commit 3136131182
14 changed files with 689 additions and 15 deletions

131
lib/tags.ts Normal file
View File

@@ -0,0 +1,131 @@
import { getAllPosts } from './markdown';
import type { Post } from './types/frontmatter';
export interface TagInfo {
name: string;
slug: string;
count: number;
}
export interface TagWithPosts {
tag: TagInfo;
posts: Post[];
}
export function slugifyTag(tag: string): string {
return tag
.toLowerCase()
.replace(/[ăâ]/g, 'a')
.replace(/[îï]/g, 'i')
.replace(/[șş]/g, 's')
.replace(/[țţ]/g, 't')
.replace(/\s+/g, '-')
.replace(/[^a-z0-9-]/g, '')
.replace(/-+/g, '-')
.replace(/^-|-$/g, '');
}
export async function getAllTags(): Promise<TagInfo[]> {
const posts = getAllPosts();
const tagMap = new Map<string, number>();
posts.forEach(post => {
const tags = post.frontmatter.tags?.filter(Boolean) || [];
tags.forEach(tag => {
const count = tagMap.get(tag) || 0;
tagMap.set(tag, count + 1);
});
});
return Array.from(tagMap.entries())
.map(([name, count]) => ({
name,
slug: slugifyTag(name),
count
}))
.sort((a, b) => b.count - a.count);
}
export async function getPostsByTag(tagSlug: string): Promise<Post[]> {
const posts = getAllPosts();
return posts.filter(post => {
const tags = post.frontmatter.tags?.filter(Boolean) || [];
return tags.some(tag => slugifyTag(tag) === tagSlug);
});
}
export async function getTagInfo(tagSlug: string): Promise<TagInfo | null> {
const allTags = await getAllTags();
return allTags.find(tag => tag.slug === tagSlug) || null;
}
export async function getPopularTags(limit = 10): Promise<TagInfo[]> {
const allTags = await getAllTags();
return allTags.slice(0, limit);
}
export async function getRelatedTags(tagSlug: string, limit = 5): Promise<TagInfo[]> {
const posts = await getPostsByTag(tagSlug);
const relatedTagMap = new Map<string, number>();
posts.forEach(post => {
const tags = post.frontmatter.tags?.filter(Boolean) || [];
tags.forEach(tag => {
const slug = slugifyTag(tag);
if (slug !== tagSlug) {
const count = relatedTagMap.get(tag) || 0;
relatedTagMap.set(tag, count + 1);
}
});
});
return Array.from(relatedTagMap.entries())
.map(([name, count]) => ({
name,
slug: slugifyTag(name),
count
}))
.sort((a, b) => b.count - a.count)
.slice(0, limit);
}
export function validateTags(tags: any): string[] {
if (!tags) return [];
if (!Array.isArray(tags)) {
console.warn('Tags should be an array');
return [];
}
const validTags = tags
.filter(tag => tag && typeof tag === 'string')
.slice(0, 3);
if (tags.length > 3) {
console.warn(`Too many tags provided (${tags.length}). Limited to first 3.`);
}
return validTags;
}
export async function getTagCloud(): Promise<Array<TagInfo & { size: 'sm' | 'md' | 'lg' | 'xl' }>> {
const tags = await getAllTags();
if (tags.length === 0) return [];
const maxCount = Math.max(...tags.map(t => t.count));
const minCount = Math.min(...tags.map(t => t.count));
const range = maxCount - minCount || 1;
return tags.map(tag => {
const normalized = (tag.count - minCount) / range;
let size: 'sm' | 'md' | 'lg' | 'xl';
if (normalized < 0.25) size = 'sm';
else if (normalized < 0.5) size = 'md';
else if (normalized < 0.75) size = 'lg';
else size = 'xl';
return { ...tag, size };
});
}