UNPKG

zemenay-blog

Version:

Zemenay Blog as a pluggable Next.js package (dedicated DB)

183 lines (144 loc) 4.97 kB
import { type ClassValue, clsx } from "clsx" import { twMerge } from "tailwind-merge" export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs)) } export function generateSlug(title: string): string { return title .toLowerCase() .trim() .replace(/[^\w\s-]/g, "") // Remove special characters .replace(/[\s_-]+/g, "-") // Replace spaces and underscores with hyphens .replace(/^-+|-+$/g, "") // Remove leading/trailing hyphens } export function generateExcerpt(content: string, maxLength = 160): string { // Remove HTML tags const plainText = content.replace(/<[^>]*>/g, "") if (plainText.length <= maxLength) { return plainText } // Find the last complete word within the limit const truncated = plainText.substring(0, maxLength) const lastSpaceIndex = truncated.lastIndexOf(" ") if (lastSpaceIndex > 0) { return truncated.substring(0, lastSpaceIndex) + "..." } return truncated + "..." } export function formatDate(date: Date): string { return new Intl.DateTimeFormat("en-US", { year: "numeric", month: "long", day: "numeric", }).format(date) } export function formatRelativeDate(date: Date | string): string { const dateObj = typeof date === 'string' ? new Date(date) : date // Check if the date is valid if (isNaN(dateObj.getTime())) { return "recently" } const now = new Date() const diffInSeconds = Math.floor((now.getTime() - dateObj.getTime()) / 1000) if (diffInSeconds < 60) { return "just now" } const diffInMinutes = Math.floor(diffInSeconds / 60) if (diffInMinutes < 60) { return `${diffInMinutes} minute${diffInMinutes > 1 ? "s" : ""} ago` } const diffInHours = Math.floor(diffInMinutes / 60) if (diffInHours < 24) { return `${diffInHours} hour${diffInHours > 1 ? "s" : ""} ago` } const diffInDays = Math.floor(diffInHours / 24) if (diffInDays < 7) { return `${diffInDays} day${diffInDays > 1 ? "s" : ""} ago` } const diffInWeeks = Math.floor(diffInDays / 7) if (diffInWeeks < 4) { return `${diffInWeeks} week${diffInWeeks > 1 ? "s" : ""} ago` } const diffInMonths = Math.floor(diffInDays / 30) if (diffInMonths < 12) { return `${diffInMonths} month${diffInMonths > 1 ? "s" : ""} ago` } const diffInYears = Math.floor(diffInDays / 365) return `${diffInYears} year${diffInYears > 1 ? "s" : ""} ago` } export function getReadingTime(content: string): number { const wordsPerMinute = 200 const words = content.trim().split(/\s+/).length const readingTime = Math.ceil(words / wordsPerMinute) return Math.max(1, readingTime) } export function debounce<T extends (...args: any[]) => any>(func: T, wait: number): (...args: Parameters<T>) => void { let timeout: NodeJS.Timeout | null = null return (...args: Parameters<T>) => { if (timeout) { clearTimeout(timeout) } timeout = setTimeout(() => { func(...args) }, wait) } } export function truncateText(text: string, maxLength: number): string { if (text.length <= maxLength) { return text } return text.substring(0, maxLength).trim() + "..." } export function validateEmail(email: string): boolean { const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/ return emailRegex.test(email) } export function validatePassword(password: string): { isValid: boolean errors: string[] } { const errors: string[] = [] if (password.length < 8) { errors.push("Password must be at least 8 characters long") } if (!/[A-Z]/.test(password)) { errors.push("Password must contain at least one uppercase letter") } if (!/[a-z]/.test(password)) { errors.push("Password must contain at least one lowercase letter") } if (!/\d/.test(password)) { errors.push("Password must contain at least one number") } return { isValid: errors.length === 0, errors, } } export function sanitizeHtml(html: string): string { // Basic HTML sanitization - in production, use a proper library like DOMPurify return html .replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, "") .replace(/<iframe\b[^<]*(?:(?!<\/iframe>)<[^<]*)*<\/iframe>/gi, "") .replace(/javascript:/gi, "") .replace(/on\w+\s*=/gi, "") } export function formatFileSize(bytes: number): string { if (bytes === 0) return "0 Bytes" const k = 1024 const sizes = ["Bytes", "KB", "MB", "GB"] const i = Math.floor(Math.log(bytes) / Math.log(k)) return Number.parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + " " + sizes[i] } export function generateRandomId(): string { return Math.random().toString(36).substring(2) + Date.now().toString(36) } export function capitalizeFirst(str: string): string { return str.charAt(0).toUpperCase() + str.slice(1) } export function pluralize(count: number, singular: string, plural?: string): string { if (count === 1) { return `${count} ${singular}` } return `${count} ${plural || singular + "s"}` }