import { useState, useEffect, useRef, useMemo, useCallback } from 'react'; import { TeknavData } from './teknav-data.js'; import { useNav, useToast, useAuth, AuthorAvatar, CategoryBadge, TypeBadge, StatusBadge, EmptyState, Header, ReadingProgress, ReadingListModal, inputStyle, pillBtn, iconBtnStyle, useViewportPrefetch, } from './teknav-ui.jsx'; import { LazyDiagram } from './src/components/lazy-diagram.jsx'; import { ArticleCard } from './teknav-home.jsx'; import { contentApi } from './src/lib/content-api.js'; import { engagementApi } from './src/lib/engagement-api.js'; import { QuoteSharer } from './src/components/QuoteSharer.jsx'; import { iconNode } from './src/lib/icon.jsx'; import crownIcon from './src/assets/icons/crown.svg'; import thumbsUpIcon from './src/assets/icons/thumbs-up.svg'; import starIcon from './src/assets/icons/star.svg'; import heartIcon from './src/assets/icons/heart.svg'; import flameIcon from './src/assets/icons/flame.svg'; import lightbulbIcon from './src/assets/icons/lightbulb.svg'; function articleDateValue(article) { return article?.dateEn || article?.publishedAt || article?.date || ''; } function articleDateLabel(article) { return article?.date || (typeof article?.publishedAt === 'string' ? article.publishedAt.slice(0, 10) : '') || article?.dateEn || ''; } const TEKRAG_SLUG = 'tekrag-persian-data-centric-rag-benchmark'; const isTekRagArticle = (articleOrSlug) => (typeof articleOrSlug === 'string' ? articleOrSlug : articleOrSlug?.slug) === TEKRAG_SLUG; function numberLabel(value, english = false) { return Number(value ?? 0).toLocaleString(english ? 'en-US' : 'fa-IR'); } function ArticleRow({ article, idx }) { const { navigate } = useNav(); const [vis, setVis] = useState(false); const ref = useRef(); useViewportPrefetch(ref, article.slug); const cat = TeknavData.categories.find(c => c.id === article.category); const author = TeknavData.authors.find(a => a.id === article.authorId); useEffect(() => { const obs = new IntersectionObserver(([e]) => { if (e.isIntersecting) setVis(true); }, { threshold: 0.05 }); if (ref.current) obs.observe(ref.current); return () => obs.disconnect(); }, []); return (
navigate('/article/' + article.slug)} onMouseEnter={e => { e.currentTarget.style.opacity = '0.85'; }} onMouseLeave={e => { e.currentTarget.style.opacity = '1'; }}>
{articleDateLabel(article)} ·

{article.title}

{article.subtitle &&
{article.subtitle}
}
{author?.specialty && · } {author?.specialty && {author.specialty}}

{article.summary}

{article.type} · {article.readTime} دقیقه مطالعه · {(article.views || 0).toLocaleString('fa')} بازدید {article.tags?.slice(0, 2).map(t => ( {t} ))}
); } function FilterBar({ filters, onChange }) { const cats = [{ id: '', name: 'همه' }, ...TeknavData.categories]; const types = ['همه', 'تحلیل عمیق', 'راهنمای فنی', 'داده‌نما', 'پژوهش', 'راستی‌آزمایی‌شده', 'پرونده']; const sorts = [{ v: 'latest', l: 'جدیدترین' }, { v: 'popular', l: 'پربازدیدترین' }, { v: 'readtime', l: 'زمان مطالعه' }]; const chip = (active) => ({ border: active ? '1.5px solid #0F6B73' : '1px solid #E4DDD2', background: active ? '#0F6B7312' : '#fff', color: active ? '#0F6B73' : '#5F6B6D', padding: '5px 12px', borderRadius: 20, fontSize: 12, cursor: 'pointer', fontFamily: 'Vazirmatn,sans-serif', fontWeight: active ? 700 : 400, transition: 'all 0.15s', whiteSpace: 'nowrap', }); return (
دسته: {cats.map(c => ( ))}
نوع: {types.map(t => ( ))}
مرتب‌سازی: {sorts.map(s => ( ))}
{filters.q && (
جستجو: «{filters.q}»
)}
); } function ArticleListPage({ initQ = '' }) { const [filters, setFilters] = useState({ cat: '', type: '', sort: 'latest', q: initQ }); const [searchInput, setSearchInput] = useState(initQ); const [page, setPage] = useState(1); const [allArticles, setAllArticles] = useState(() => contentApi.fallbackArticles()); const PER = 8; useEffect(() => { let cancelled = false; contentApi.listArticles({ limit: 100 }) .then((res) => { if (!cancelled) setAllArticles(res.items ?? []); }) .catch(() => {}); return () => { cancelled = true; }; }, []); const filtered = useMemo(() => { let list = allArticles; if (filters.q) { const q = filters.q.toLowerCase(); list = list.filter(a => a.title.toLowerCase().includes(q) || a.summary.toLowerCase().includes(q) || a.authorName?.toLowerCase().includes(q) || a.categoryName?.toLowerCase().includes(q)); } if (filters.cat) list = list.filter(a => a.category === filters.cat); if (filters.type) list = list.filter(a => a.type === filters.type); if (filters.sort === 'popular') list = [...list].sort((a, b) => b.views - a.views); else if (filters.sort === 'readtime') list = [...list].sort((a, b) => b.readTime - a.readTime); else list = [...list].sort((a, b) => articleDateValue(b).localeCompare(articleDateValue(a))); return list; }, [filters, allArticles]); const visible = filtered.slice(0, page * PER); const hasMore = visible.length < filtered.length; const doSearch = (e) => { e.preventDefault(); setFilters(f => ({ ...f, q: searchInput })); setPage(1); }; return (

همه مقاله‌ها

{filtered.length} مقاله یافت شد

setSearchInput(e.target.value)} placeholder="جستجو در عنوان، خلاصه، نویسنده..." style={{ flex: 1, ...inputStyle }} dir="rtl" />
{ setFilters(f); setPage(1); }} /> {visible.length === 0 ? ( ) : ( <> {visible.map((a, i) => )} {hasMore && (
)} )}
); } function TableOfContents({ headings, english = false }) { const [active, setActive] = useState(null); useEffect(() => { const obs = new IntersectionObserver(entries => { entries.forEach(e => { if (e.isIntersecting) setActive(e.target.id); }); }, { rootMargin: '-20% 0px -70% 0px' }); headings.forEach(h => { const el = document.getElementById(h.id); if (el) obs.observe(el); }); return () => obs.disconnect(); }, [headings]); return ( ); } function EmbeddedDiagramCard({ title, subtitle, type, english = false }) { return (
{title}
{subtitle &&
{subtitle}
}
); } function ArticleEmbeddedDiagrams({ slug }) { if (isTekRagArticle(slug)) { return null; } if (slug === 'ai-clean-data') { return ( ); } if (slug === 'ai-agents-2026' || slug === 'agentic-ai-production') { return (
); } return null; } function SeriesNavBanner({ series, currentSlug }) { const { navigate } = useNav(); const idx = series.articles.findIndex(a => a.slug === currentSlug); const prev = idx > 0 ? series.articles[idx - 1] : null; const next = idx < series.articles.length - 1 ? series.articles[idx + 1] : null; if (!prev && !next) return null; const btnBase = { background: 'transparent', border: '1px solid #0F6B7330', borderRadius: 8, padding: '10px 16px', cursor: 'pointer', fontFamily: 'Vazirmatn,sans-serif', color: '#0F6B73', fontSize: 13, fontWeight: 600, maxWidth: 280, textAlign: 'right', transition: 'all 0.15s', lineHeight: 1.5, }; return (
{prev ? ( ) :
} {next ? ( ) :
}
); } function ArticleQaSection({ slug, english = false }) { const { user } = useAuth(); const [items, setItems] = useState([]); const [loading, setLoading] = useState(true); const [body, setBody] = useState(''); const [replyTo, setReplyTo] = useState(null); const toast = useToast(); const load = useCallback(() => { setLoading(true); engagementApi.listQa(slug) .then(res => setItems(res.items ?? [])) .finally(() => setLoading(false)); }, [slug]); useEffect(() => { load(); }, [load]); const submit = async (e) => { e.preventDefault(); if (!body.trim()) return; try { if (replyTo) await engagementApi.postAnswer(replyTo.id, body); else await engagementApi.postQuestion(slug, body); setBody(''); setReplyTo(null); toast(english ? 'Submitted' : 'ثبت شد'); load(); } catch { toast(english ? 'Submission failed' : 'خطا در ثبت', 'error'); } }; return (

{english ? 'Questions and Answers' : 'پرسش و پاسخ'}

{user ? (
{replyTo &&
{english ? 'Replying to' : 'پاسخ به'}: {replyTo.author?.name}
}