import { clsx, type ClassValue } from 'clsx' import { twMerge } from 'tailwind-merge' export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs)) } const AR_FA_TO_EN_DIGITS: Record = { '۰': '0', '۱': '1', '۲': '2', '۳': '3', '۴': '4', '۵': '5', '۶': '6', '۷': '7', '۸': '8', '۹': '9', '٠': '0', '١': '1', '٢': '2', '٣': '3', '٤': '4', '٥': '5', '٦': '6', '٧': '7', '٨': '8', '٩': '9', } /** * تبدیل تمامی ارقام فارسی و عربی به ارقام استاندارد انگلیسی */ export const toEn = (n: number | string | null | undefined): string => { if (n == null) return '' return String(n).replace(/[۰-۹٠-٩]/g, (d) => AR_FA_TO_EN_DIGITS[d] ?? d) } /** * طبق استاندارد پنل عقیله، کلیه اعداد در سراسر پنل به صورت انگلیسی نمایش داده می‌شوند. * این تابع به عنوان آداپتور به toEn متصل است تا هیچ کدی نشکند و ارقام انگلیسی تولید شوند. */ export const toFa = toEn /** * استخراج URL تصویر از thumbnailِ خامِ بک‌اند. * ورودی می‌تواند رشته باشد یا آبجکتی مثل { sm, md, lg } یا { thumb, medium, large }. */ export function thumbnailUrl(thumbnail: unknown): string | null { if (!thumbnail) return null if (typeof thumbnail === 'string') return thumbnail.trim() || null if (typeof thumbnail === 'object') { const o = thumbnail as Record const v = o.md ?? o.medium ?? o.lg ?? o.large ?? o.sm ?? o.thumb ?? Object.values(o)[0] return v != null ? String(v).trim() || null : null } return null } export const REMOTE_MEDIA_ORIGIN = 'https://aqila.newhorizonco.uk' /** * دریافت آدرس جایگزین از سرور اصلی ریموت در صورتی که تصویر در لوکال هاست موجود نباشد */ export function getMediaFallbackUrl(url: string | null | undefined): string | null { if (!url) return null const trimmed = String(url).trim() if (!trimmed || trimmed.startsWith('data:') || trimmed.startsWith('blob:')) return null if (trimmed.startsWith(REMOTE_MEDIA_ORIGIN)) return trimmed const cleanPath = trimmed .replace(/^https?:\/\/(?:localhost|127\.0\.0\.1)(?::\d+)?\//i, '') .replace(/^\/?(media\/)?/, '') return `${REMOTE_MEDIA_ORIGIN}/media/${cleanPath}` } /** * استانداردسازی آدرس تصاویر و فایل‌های چندرسانه‌ای * آدرس‌های نسبی، فاقد پروتکل یا لوکال‌هاست را به آدرس معتبر و قابل رندر تبدیل می‌کند. */ export function resolveMediaUrl(url: string | null | undefined): string { if (!url) return '' const trimmed = String(url).trim() if (!trimmed) return '' if (trimmed.startsWith('data:') || trimmed.startsWith('blob:')) { return trimmed } // تبدیل URL کامل لوکال‌هاست به مسیر نسبی تا از طریق پراکسیِ Vite بدون خطای CORS بارگذاری شود const localMatch = trimmed.match(/^https?:\/\/(?:localhost|127\.0\.0\.1)(?::\d+)?\/(media|static)\/(.*)$/i) if (localMatch && localMatch[1] && localMatch[2]) { return `/${localMatch[1]}/${localMatch[2]}` } if (/^https?:\/\//i.test(trimmed)) { return trimmed } if (trimmed.startsWith('media/') || trimmed.startsWith('static/')) { return `/${trimmed}` } if (trimmed.startsWith('factors/')) { return `/media/${trimmed}` } return trimmed.startsWith('/') ? trimmed : `/${trimmed}` } /** ثانیه → «م:ثث» با ارقام فارسی (مثلاً ۳:۰۵). مقادیر نامعتبر → ۰:۰۰ */ export function formatTime(seconds: number): string { if (!Number.isFinite(seconds) || seconds < 0) seconds = 0 const m = Math.floor(seconds / 60) const s = Math.floor(seconds % 60) return toFa(`${m}:${String(s).padStart(2, '0')}`) }