🏷️ added tags system
This commit is contained in:
131
lib/tags.ts
Normal file
131
lib/tags.ts
Normal 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 };
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user