💂♂️ fixed lint and prittier
This commit is contained in:
114
lib/markdown.ts
114
lib/markdown.ts
@@ -1,43 +1,43 @@
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import matter from 'gray-matter';
|
||||
import { FrontMatter, Post } from './types/frontmatter';
|
||||
import { generateExcerpt } from './utils';
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
import matter from 'gray-matter'
|
||||
import { FrontMatter, Post } from './types/frontmatter'
|
||||
import { generateExcerpt } from './utils'
|
||||
|
||||
const POSTS_PATH = path.join(process.cwd(), 'content', 'blog');
|
||||
const POSTS_PATH = path.join(process.cwd(), 'content', 'blog')
|
||||
|
||||
export function sanitizePath(inputPath: string): string {
|
||||
const normalized = path.normalize(inputPath).replace(/^(\.\.[\/\\])+/, '');
|
||||
const normalized = path.normalize(inputPath).replace(/^(\.\.[/\\])+/, '')
|
||||
if (normalized.includes('..') || path.isAbsolute(normalized)) {
|
||||
throw new Error('Invalid path');
|
||||
throw new Error('Invalid path')
|
||||
}
|
||||
return normalized;
|
||||
return normalized
|
||||
}
|
||||
|
||||
export function calculateReadingTime(content: string): number {
|
||||
const wordsPerMinute = 200;
|
||||
const words = content.trim().split(/\s+/).length;
|
||||
return Math.ceil(words / wordsPerMinute);
|
||||
const wordsPerMinute = 200
|
||||
const words = content.trim().split(/\s+/).length
|
||||
return Math.ceil(words / wordsPerMinute)
|
||||
}
|
||||
|
||||
export function validateFrontmatter(data: any): FrontMatter {
|
||||
if (!data.title || typeof data.title !== 'string') {
|
||||
throw new Error('Invalid title');
|
||||
throw new Error('Invalid title')
|
||||
}
|
||||
if (!data.description || typeof data.description !== 'string') {
|
||||
throw new Error('Invalid description');
|
||||
throw new Error('Invalid description')
|
||||
}
|
||||
if (!data.date || typeof data.date !== 'string') {
|
||||
throw new Error('Invalid date');
|
||||
throw new Error('Invalid date')
|
||||
}
|
||||
if (!data.author || typeof data.author !== 'string') {
|
||||
throw new Error('Invalid author');
|
||||
throw new Error('Invalid author')
|
||||
}
|
||||
if (!data.category || typeof data.category !== 'string') {
|
||||
throw new Error('Invalid category');
|
||||
throw new Error('Invalid category')
|
||||
}
|
||||
if (!Array.isArray(data.tags) || data.tags.length === 0 || data.tags.length > 3) {
|
||||
throw new Error('Tags must be array with 1-3 items');
|
||||
throw new Error('Tags must be array with 1-3 items')
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -49,21 +49,21 @@ export function validateFrontmatter(data: any): FrontMatter {
|
||||
tags: data.tags,
|
||||
image: data.image,
|
||||
draft: data.draft || false,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export function getPostBySlug(slug: string | string[]): Post | null {
|
||||
const slugArray = Array.isArray(slug) ? slug : slug.split('/');
|
||||
const sanitized = slugArray.map(s => sanitizePath(s));
|
||||
const fullPath = path.join(POSTS_PATH, ...sanitized) + '.md';
|
||||
const slugArray = Array.isArray(slug) ? slug : slug.split('/')
|
||||
const sanitized = slugArray.map(s => sanitizePath(s))
|
||||
const fullPath = path.join(POSTS_PATH, ...sanitized) + '.md'
|
||||
|
||||
if (!fs.existsSync(fullPath)) {
|
||||
return null;
|
||||
return null
|
||||
}
|
||||
|
||||
const fileContents = fs.readFileSync(fullPath, 'utf8');
|
||||
const { data, content } = matter(fileContents);
|
||||
const frontmatter = validateFrontmatter(data);
|
||||
const fileContents = fs.readFileSync(fullPath, 'utf8')
|
||||
const { data, content } = matter(fileContents)
|
||||
const frontmatter = validateFrontmatter(data)
|
||||
|
||||
return {
|
||||
slug: sanitized.join('/'),
|
||||
@@ -71,84 +71,86 @@ export function getPostBySlug(slug: string | string[]): Post | null {
|
||||
content,
|
||||
readingTime: calculateReadingTime(content),
|
||||
excerpt: generateExcerpt(content),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export function getAllPosts(includeContent = false): Post[] {
|
||||
const posts: Post[] = [];
|
||||
const posts: Post[] = []
|
||||
|
||||
function walkDir(dir: string, prefix = ''): void {
|
||||
const files = fs.readdirSync(dir);
|
||||
const files = fs.readdirSync(dir)
|
||||
|
||||
for (const file of files) {
|
||||
const filePath = path.join(dir, file);
|
||||
const stat = fs.statSync(filePath);
|
||||
const filePath = path.join(dir, file)
|
||||
const stat = fs.statSync(filePath)
|
||||
|
||||
if (stat.isDirectory()) {
|
||||
walkDir(filePath, prefix ? `${prefix}/${file}` : file);
|
||||
walkDir(filePath, prefix ? `${prefix}/${file}` : file)
|
||||
} else if (file.endsWith('.md')) {
|
||||
const slug = prefix ? `${prefix}/${file.replace(/\.md$/, '')}` : file.replace(/\.md$/, '');
|
||||
const slug = prefix ? `${prefix}/${file.replace(/\.md$/, '')}` : file.replace(/\.md$/, '')
|
||||
try {
|
||||
const post = getPostBySlug(slug.split('/'));
|
||||
const post = getPostBySlug(slug.split('/'))
|
||||
if (post && !post.frontmatter.draft) {
|
||||
posts.push(includeContent ? post : { ...post, content: '' });
|
||||
posts.push(includeContent ? post : { ...post, content: '' })
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Error loading post ${slug}:`, error);
|
||||
console.error(`Error loading post ${slug}:`, error)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (fs.existsSync(POSTS_PATH)) {
|
||||
walkDir(POSTS_PATH);
|
||||
walkDir(POSTS_PATH)
|
||||
}
|
||||
|
||||
return posts.sort((a, b) => new Date(b.frontmatter.date).getTime() - new Date(a.frontmatter.date).getTime());
|
||||
return posts.sort(
|
||||
(a, b) => new Date(b.frontmatter.date).getTime() - new Date(a.frontmatter.date).getTime()
|
||||
)
|
||||
}
|
||||
|
||||
export async function getRelatedPosts(currentSlug: string, limit = 3): Promise<Post[]> {
|
||||
const currentPost = getPostBySlug(currentSlug);
|
||||
if (!currentPost) return [];
|
||||
const currentPost = getPostBySlug(currentSlug)
|
||||
if (!currentPost) return []
|
||||
|
||||
const allPosts = getAllPosts(false);
|
||||
const { category, tags } = currentPost.frontmatter;
|
||||
const allPosts = getAllPosts(false)
|
||||
const { category, tags } = currentPost.frontmatter
|
||||
|
||||
const scored = allPosts
|
||||
.filter(post => post.slug !== currentSlug)
|
||||
.map(post => {
|
||||
let score = 0;
|
||||
if (post.frontmatter.category === category) score += 3;
|
||||
score += post.frontmatter.tags.filter(tag => tags.includes(tag)).length * 2;
|
||||
return { post, score };
|
||||
let score = 0
|
||||
if (post.frontmatter.category === category) score += 3
|
||||
score += post.frontmatter.tags.filter(tag => tags.includes(tag)).length * 2
|
||||
return { post, score }
|
||||
})
|
||||
.filter(({ score }) => score > 0)
|
||||
.sort((a, b) => b.score - a.score);
|
||||
.sort((a, b) => b.score - a.score)
|
||||
|
||||
return scored.slice(0, limit).map(({ post }) => post);
|
||||
return scored.slice(0, limit).map(({ post }) => post)
|
||||
}
|
||||
|
||||
export function getAllPostSlugs(): string[][] {
|
||||
const slugs: string[][] = [];
|
||||
const slugs: string[][] = []
|
||||
|
||||
function walkDir(dir: string, prefix: string[] = []): void {
|
||||
const files = fs.readdirSync(dir);
|
||||
const files = fs.readdirSync(dir)
|
||||
|
||||
for (const file of files) {
|
||||
const filePath = path.join(dir, file);
|
||||
const stat = fs.statSync(filePath);
|
||||
const filePath = path.join(dir, file)
|
||||
const stat = fs.statSync(filePath)
|
||||
|
||||
if (stat.isDirectory()) {
|
||||
walkDir(filePath, [...prefix, file]);
|
||||
walkDir(filePath, [...prefix, file])
|
||||
} else if (file.endsWith('.md')) {
|
||||
slugs.push([...prefix, file.replace(/\.md$/, '')]);
|
||||
slugs.push([...prefix, file.replace(/\.md$/, '')])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (fs.existsSync(POSTS_PATH)) {
|
||||
walkDir(POSTS_PATH);
|
||||
walkDir(POSTS_PATH)
|
||||
}
|
||||
|
||||
return slugs;
|
||||
return slugs
|
||||
}
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
export interface FrontMatter {
|
||||
title: string;
|
||||
description: string;
|
||||
date: string;
|
||||
author: string;
|
||||
category: string;
|
||||
tags: string[];
|
||||
image?: string;
|
||||
draft?: boolean;
|
||||
title: string
|
||||
description: string
|
||||
date: string
|
||||
author: string
|
||||
category: string
|
||||
tags: string[]
|
||||
image?: string
|
||||
draft?: boolean
|
||||
}
|
||||
|
||||
export interface Post {
|
||||
slug: string;
|
||||
frontmatter: FrontMatter;
|
||||
content: string;
|
||||
readingTime: number;
|
||||
excerpt: string;
|
||||
slug: string
|
||||
frontmatter: FrontMatter
|
||||
content: string
|
||||
readingTime: number
|
||||
excerpt: string
|
||||
}
|
||||
|
||||
export interface BlogParams {
|
||||
slug: string[];
|
||||
slug: string[]
|
||||
}
|
||||
|
||||
68
lib/utils.ts
68
lib/utils.ts
@@ -1,47 +1,65 @@
|
||||
export function formatDate(dateString: string): string {
|
||||
const date = new Date(dateString);
|
||||
const date = new Date(dateString)
|
||||
const months = [
|
||||
'ianuarie', 'februarie', 'martie', 'aprilie', 'mai', 'iunie',
|
||||
'iulie', 'august', 'septembrie', 'octombrie', 'noiembrie', 'decembrie'
|
||||
];
|
||||
'ianuarie',
|
||||
'februarie',
|
||||
'martie',
|
||||
'aprilie',
|
||||
'mai',
|
||||
'iunie',
|
||||
'iulie',
|
||||
'august',
|
||||
'septembrie',
|
||||
'octombrie',
|
||||
'noiembrie',
|
||||
'decembrie',
|
||||
]
|
||||
|
||||
return `${date.getDate()} ${months[date.getMonth()]} ${date.getFullYear()}`;
|
||||
return `${date.getDate()} ${months[date.getMonth()]} ${date.getFullYear()}`
|
||||
}
|
||||
|
||||
export function formatRelativeDate(dateString: string): string {
|
||||
const date = new Date(dateString);
|
||||
const now = new Date();
|
||||
const diffTime = Math.abs(now.getTime() - date.getTime());
|
||||
const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
|
||||
const date = new Date(dateString)
|
||||
const now = new Date()
|
||||
const diffTime = Math.abs(now.getTime() - date.getTime())
|
||||
const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24))
|
||||
|
||||
if (diffDays === 0) return 'astăzi';
|
||||
if (diffDays === 1) return 'ieri';
|
||||
if (diffDays < 7) return `acum ${diffDays} zile`;
|
||||
if (diffDays < 30) return `acum ${Math.floor(diffDays / 7)} săptămâni`;
|
||||
if (diffDays < 365) return `acum ${Math.floor(diffDays / 30)} luni`;
|
||||
return `acum ${Math.floor(diffDays / 365)} ani`;
|
||||
if (diffDays === 0) return 'astăzi'
|
||||
if (diffDays === 1) return 'ieri'
|
||||
if (diffDays < 7) return `acum ${diffDays} zile`
|
||||
if (diffDays < 30) return `acum ${Math.floor(diffDays / 7)} săptămâni`
|
||||
if (diffDays < 365) return `acum ${Math.floor(diffDays / 30)} luni`
|
||||
return `acum ${Math.floor(diffDays / 365)} ani`
|
||||
}
|
||||
|
||||
export function generateExcerpt(content: string, maxLength = 160): string {
|
||||
const text = content
|
||||
.replace(/^---[\s\S]*?---/, '')
|
||||
.replace(/!\[.*?\]\(.*?\)/g, '')
|
||||
.replace(/\[([^\]]+)\]\([^\)]+\)/g, '$1')
|
||||
.replace(/\[([^\]]+)\]\([^)]+\)/g, '$1')
|
||||
.replace(/[#*`]/g, '')
|
||||
.trim();
|
||||
.trim()
|
||||
|
||||
if (text.length <= maxLength) return text;
|
||||
if (text.length <= maxLength) return text
|
||||
|
||||
const truncated = text.slice(0, maxLength);
|
||||
const lastSpace = truncated.lastIndexOf(' ');
|
||||
return truncated.slice(0, lastSpace) + '...';
|
||||
const truncated = text.slice(0, maxLength)
|
||||
const lastSpace = truncated.lastIndexOf(' ')
|
||||
return truncated.slice(0, lastSpace) + '...'
|
||||
}
|
||||
|
||||
export function generateSlug(title: string): string {
|
||||
const romanianMap: Record<string, string> = {
|
||||
'ă': 'a', 'â': 'a', 'î': 'i', 'ș': 's', 'ț': 't',
|
||||
'Ă': 'a', 'Â': 'a', 'Î': 'i', 'Ș': 's', 'Ț': 't'
|
||||
};
|
||||
ă: 'a',
|
||||
â: 'a',
|
||||
î: 'i',
|
||||
ș: 's',
|
||||
ț: 't',
|
||||
Ă: 'a',
|
||||
Â: 'a',
|
||||
Î: 'i',
|
||||
Ș: 's',
|
||||
Ț: 't',
|
||||
}
|
||||
|
||||
return title
|
||||
.split('')
|
||||
@@ -49,5 +67,5 @@ export function generateSlug(title: string): string {
|
||||
.join('')
|
||||
.toLowerCase()
|
||||
.replace(/[^a-z0-9]+/g, '-')
|
||||
.replace(/^-+|-+$/g, '');
|
||||
.replace(/^-+|-+$/g, '')
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user