@fullstackdatasolutions/articles 0.1.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/ArticleCategoryGrid.tsx","../src/ArticleSchemas.tsx","../src/ArticleSearchBar.tsx","../src/ArticlesHero.tsx","../src/FeaturedArticle.tsx","../src/LatestArticlesSection.tsx","../src/LatestArticles.tsx","../src/ArticleCard.tsx","../src/articlesConfig.ts","../src/useArticles.ts","../src/ArticlesPage.tsx","../src/CategoryArticlesPage.tsx","../src/CommentsSection.tsx","../src/CommentForm.tsx","../src/CommentItem.tsx","../src/CommentThread.tsx"],"sourcesContent":["'use client'\n\nimport Image from 'next/image'\nimport Link from 'next/link'\nimport { useState, useEffect } from 'react'\nimport type { CategoryInfo } from './articleTypes'\n\ntype ArticleCategoryGridProps = Readonly<{\n categories: CategoryInfo[]\n pageSize?: number\n}>\n\nexport function ArticleCategoryGrid({ categories, pageSize = 8 }: ArticleCategoryGridProps) {\n const [visibleCount, setVisibleCount] = useState(pageSize)\n\n useEffect(() => {\n setVisibleCount(pageSize)\n }, [categories, pageSize])\n\n if (categories.length === 0) return null\n\n const visible = categories.slice(0, visibleCount)\n const hasMore = visibleCount < categories.length\n\n return (\n <section className=\"py-16 bg-background\">\n <div className=\"max-w-7xl mx-auto px-4 sm:px-6 lg:px-8\">\n <div className=\"text-center mb-12\">\n <h2 className=\"text-3xl font-bold text-foreground mb-4\">Browse by Category</h2>\n <p className=\"text-lg text-muted-foreground\">\n Explore our articles by topic to find the insights most relevant to your interests.\n </p>\n </div>\n\n <div className=\"grid grid-cols-2 sm:grid-cols-4 gap-6\">\n {visible.map((cat) => (\n <Link key={cat.slug} href={`/articles/category/${cat.slug}`}>\n <div className=\"group overflow-hidden rounded-xl border border-border shadow-sm hover:shadow-lg transition-all duration-300 cursor-pointer bg-card\">\n <div className=\"relative h-52 overflow-hidden\">\n <Image\n src={cat.featuredImage}\n alt={cat.name}\n fill\n sizes=\"(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw\"\n className=\"object-cover group-hover:scale-105 transition-transform duration-300\"\n />\n </div>\n <div className=\"p-4 text-center\">\n <h3 className=\"font-semibold text-foreground text-base\">{cat.name}</h3>\n <p className=\"text-sm text-muted-foreground mt-1\">\n {cat.count} {cat.count === 1 ? 'article' : 'articles'}\n </p>\n </div>\n </div>\n </Link>\n ))}\n </div>\n\n {hasMore && (\n <div className=\"mt-10 text-center\">\n <button\n type=\"button\"\n onClick={() => setVisibleCount((c) => c + pageSize)}\n className=\"inline-flex items-center justify-center px-4 py-2 border border-input bg-background hover:bg-accent rounded-md font-medium text-sm transition-colors\"\n >\n Load more categories\n </button>\n </div>\n )}\n </div>\n </section>\n )\n}\n","import { Article } from './articleTypes'\n\ntype ArticleSchemaProps = Readonly<{\n article: Article\n articleUrl: string\n siteName: string\n}>\n\nexport function ArticleSchema({ article, articleUrl, siteName }: ArticleSchemaProps) {\n const schema = {\n '@context': 'https://schema.org',\n '@type': 'Article',\n headline: article.title,\n description: article.excerpt,\n image: article.featuredImage,\n ...(article.date && { datePublished: new Date(article.date).toISOString() }),\n author: { '@type': 'Person', name: article.author },\n publisher: { '@type': 'Organization', name: siteName },\n mainEntityOfPage: { '@type': 'WebPage', '@id': articleUrl },\n }\n\n return (\n <script\n type=\"application/ld+json\"\n dangerouslySetInnerHTML={{ __html: JSON.stringify(schema) }}\n />\n )\n}\n\ntype BreadcrumbSchemaProps = Readonly<{\n items: ReadonlyArray<{ name: string; url: string }>\n}>\n\nexport function BreadcrumbSchema({ items }: BreadcrumbSchemaProps) {\n const schema = {\n '@context': 'https://schema.org',\n '@type': 'BreadcrumbList',\n itemListElement: items.map((item, index) => ({\n '@type': 'ListItem',\n position: index + 1,\n name: item.name,\n item: item.url,\n })),\n }\n\n return (\n <script\n type=\"application/ld+json\"\n dangerouslySetInnerHTML={{ __html: JSON.stringify(schema) }}\n />\n )\n}\n\ntype CollectionPageSchemaProps = Readonly<{\n title: string\n description: string\n url: string\n articleCount: number\n}>\n\nexport function CollectionPageSchema({\n title,\n description,\n url,\n articleCount,\n}: CollectionPageSchemaProps) {\n const schema = {\n '@context': 'https://schema.org',\n '@type': 'CollectionPage',\n name: title,\n description,\n url,\n numberOfItems: articleCount,\n }\n\n return (\n <script\n type=\"application/ld+json\"\n dangerouslySetInnerHTML={{ __html: JSON.stringify(schema) }}\n />\n )\n}\n","import { Search, X } from 'lucide-react'\n\ntype ArticleSearchBarProps = Readonly<{\n value: string\n onChange: (query: string) => void\n disabled?: boolean\n resultCount?: number\n}>\n\nexport function ArticleSearchBar({\n value,\n onChange,\n disabled,\n resultCount,\n}: ArticleSearchBarProps) {\n return (\n <section className=\"py-8 bg-background border-b border-border\">\n <div className=\"max-w-4xl mx-auto px-4 sm:px-6 lg:px-8\">\n <div className=\"relative\">\n <Search className=\"absolute left-3 top-1/2 transform -translate-y-1/2 text-muted-foreground h-5 w-5\" />\n <input\n type=\"text\"\n placeholder=\"Search articles (3+ characters required)...\"\n value={value}\n onChange={(e) => onChange(e.target.value)}\n disabled={disabled}\n className=\"flex w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 pl-10 pr-10 py-3 text-lg\"\n />\n {value && (\n <button\n type=\"button\"\n onClick={() => onChange('')}\n aria-label=\"Clear search\"\n className=\"absolute right-3 top-1/2 transform -translate-y-1/2 text-muted-foreground hover:text-foreground\"\n >\n <X className=\"h-5 w-5\" />\n </button>\n )}\n </div>\n {value && (\n <div className=\"mt-2 text-sm text-muted-foreground\">\n {resultCount === 0\n ? 'No articles found matching your search.'\n : `Found ${resultCount} article${resultCount === 1 ? '' : 's'} matching \"${value}\"`}\n </div>\n )}\n </div>\n </section>\n )\n}\n","import Link from 'next/link'\n\nexport function ArticlesHero() {\n return (\n <section className=\"bg-linear-to-r from-gray-50 to-gray-100 py-16 md:py-24\">\n <div className=\"max-w-7xl mx-auto px-4 sm:px-6 lg:px-8\">\n <div className=\"max-w-3xl mx-auto text-center\">\n <h1 className=\"text-4xl md:text-5xl font-bold text-foreground mb-6\">\n Vox Populus Insights\n </h1>\n <p className=\"text-xl text-muted-foreground mb-8\">\n Expert analysis, campaign strategies, and voter engagement insights from the frontlines\n of democracy.\n </p>\n <div className=\"flex flex-col sm:flex-row gap-4 justify-center\">\n <Link\n href=\"#latest\"\n className=\"inline-flex items-center justify-center px-4 py-2 bg-primary hover:bg-primary/90 text-primary-foreground rounded-md font-medium text-sm transition-colors\"\n >\n Browse Latest Articles\n </Link>\n <Link\n href=\"/contact\"\n className=\"inline-flex items-center justify-center px-4 py-2 border border-primary text-primary hover:bg-primary/10 rounded-md font-medium text-sm transition-colors\"\n >\n Contact Our Team\n </Link>\n </div>\n </div>\n </div>\n </section>\n )\n}\n","import Link from 'next/link'\nimport Image from 'next/image'\nimport { ArrowRight, Calendar, Clock, User } from 'lucide-react'\nimport type { Article } from './articleTypes'\n\ntype FeaturedArticleProps = Readonly<{\n article: Article\n}>\n\nexport function FeaturedArticle({ article }: FeaturedArticleProps) {\n return (\n <section className=\"py-16 bg-background\">\n <div className=\"max-w-7xl mx-auto px-4 sm:px-6 lg:px-8\">\n <div className=\"grid lg:grid-cols-2 gap-12 items-center\">\n <div>\n <span className=\"inline-block bg-primary/10 text-primary text-sm font-medium px-3 py-1 rounded-full mb-4\">\n FEATURED ARTICLE\n </span>\n <h2 className=\"text-3xl font-bold text-foreground mb-4\">{article.title}</h2>\n <p className=\"text-lg text-muted-foreground mb-6\">{article.excerpt}</p>\n <div className=\"flex items-center gap-6 text-sm text-muted-foreground mb-6\">\n {article.date && (\n <div className=\"flex items-center gap-2\">\n <Calendar className=\"h-4 w-4\" />\n <span>{article.date}</span>\n </div>\n )}\n <div className=\"flex items-center gap-2\">\n <Clock className=\"h-4 w-4\" />\n <span>{article.readTime}</span>\n </div>\n <div className=\"flex items-center gap-2\">\n <User className=\"h-4 w-4\" />\n <span>{article.author}</span>\n </div>\n </div>\n <Link\n href={`/articles/${article.slug}`}\n className=\"inline-flex items-center justify-center px-4 py-2 bg-primary hover:bg-primary/90 text-primary-foreground rounded-md font-medium text-sm transition-colors\"\n >\n Read Full Article\n <ArrowRight className=\"ml-2 h-4 w-4\" />\n </Link>\n </div>\n <div className=\"bg-card rounded-lg p-8\">\n <Image\n src={article.featuredImage}\n alt={article.title}\n width={800}\n height={450}\n className=\"w-full h-64 object-cover rounded-lg mb-4\"\n />\n <div className=\"flex gap-2\">\n <span className=\"bg-primary/10 text-primary text-xs font-medium px-2 py-1 rounded\">\n {article.category}\n </span>\n </div>\n </div>\n </div>\n </div>\n </section>\n )\n}\n","'use client'\n\nimport { Search } from 'lucide-react'\nimport type { Article } from './articleTypes'\nimport { LatestArticles } from './LatestArticles'\n\ninterface LatestArticlesSectionProps {\n readonly articles: Article[]\n readonly searchQuery: string\n readonly onClearSearch: () => void\n readonly pageSize?: number\n}\n\nfunction DescriptionText({\n searchQuery,\n count,\n}: {\n readonly searchQuery: string\n readonly count: number\n}) {\n const pluralSuffix = count === 1 ? '' : 's'\n const text = searchQuery\n ? `Found ${count} article${pluralSuffix} matching your search.`\n : 'Stay informed with our latest insights on voter engagement, campaign strategies, and civic technology.'\n return <p className=\"text-lg text-muted-foreground\">{text}</p>\n}\n\nexport function LatestArticlesSection({\n articles,\n searchQuery,\n onClearSearch,\n pageSize = 6,\n}: Readonly<LatestArticlesSectionProps>) {\n return (\n <section id=\"latest\" className=\"py-16 bg-muted/50\">\n <div className=\"max-w-7xl mx-auto px-4 sm:px-6 lg:px-8\">\n <div className=\"text-center mb-12\">\n <h2 className=\"text-3xl font-bold text-foreground mb-4\">\n {searchQuery ? 'Search Results' : 'Latest Articles'}\n </h2>\n <DescriptionText searchQuery={searchQuery} count={articles.length} />\n </div>\n\n {articles.length === 0 && searchQuery ? (\n <div className=\"text-center py-12\">\n <div className=\"max-w-md mx-auto\">\n <Search className=\"h-12 w-12 text-muted-foreground mx-auto mb-4\" />\n <h3 className=\"text-lg font-semibold text-foreground mb-2\">No articles found</h3>\n <p className=\"text-muted-foreground mb-4\">\n We couldn&apos;t find any articles matching &quot;{searchQuery}&quot;. Try adjusting\n your search terms.\n </p>\n <button\n type=\"button\"\n onClick={onClearSearch}\n className=\"inline-flex items-center justify-center px-4 py-2 border border-input bg-background hover:bg-accent rounded-md font-medium text-sm transition-colors\"\n >\n Clear search\n </button>\n </div>\n </div>\n ) : (\n <LatestArticles articles={articles} pageSize={pageSize} key={searchQuery} />\n )}\n </div>\n </section>\n )\n}\n","'use client'\n\nimport { useState, useEffect } from 'react'\nimport type { Article } from './articleTypes'\nimport { ArticleCard } from './ArticleCard'\n\ninterface LatestArticlesProps {\n readonly articles: Article[]\n readonly pageSize: number\n}\n\nexport function LatestArticles({ articles, pageSize }: Readonly<LatestArticlesProps>) {\n const [visibleCount, setVisibleCount] = useState(pageSize)\n\n useEffect(() => {\n setVisibleCount(pageSize)\n }, [articles, pageSize])\n\n const visible = articles.slice(0, visibleCount)\n const hasMore = visibleCount < articles.length\n\n return (\n <div>\n <div className=\"grid md:grid-cols-2 lg:grid-cols-3 gap-8\">\n {visible.map((article) => (\n <ArticleCard key={article.slug} article={article} />\n ))}\n </div>\n {hasMore && (\n <div className=\"mt-10 text-center\">\n <button\n type=\"button\"\n onClick={() => setVisibleCount((c) => c + pageSize)}\n className=\"inline-flex items-center justify-center px-4 py-2 border border-input bg-background hover:bg-accent rounded-md font-medium text-sm transition-colors\"\n >\n Load more articles\n </button>\n </div>\n )}\n </div>\n )\n}\n","'use client'\n\nimport Image from 'next/image'\nimport Link from 'next/link'\nimport { ArrowRight, Calendar, Clock } from 'lucide-react'\nimport type { Article } from './articleTypes'\n\ntype ArticleCardProps = Readonly<{\n article: Article\n}>\n\nexport function ArticleCard({ article }: ArticleCardProps) {\n return (\n <div className=\"rounded-lg border border-border bg-card text-card-foreground shadow-sm hover:shadow-lg transition-shadow duration-300\">\n <div className=\"p-0\">\n <Image\n src={article.featuredImage}\n alt={article.title}\n className=\"w-full h-48 object-cover rounded-t-lg\"\n width={400}\n height={200}\n />\n </div>\n <div className=\"p-6\">\n <div className=\"flex gap-2 mb-3\">\n <span className=\"bg-primary/10 text-primary text-xs font-medium px-2 py-1 rounded\">\n {article.category}\n </span>\n </div>\n <h3 className=\"text-lg font-semibold leading-none tracking-tight mb-2\">\n <Link\n href={`/articles/${article.slug}`}\n className=\"hover:text-indigo-600 transition-colors\"\n >\n {article.title}\n </Link>\n </h3>\n <p className=\"text-muted-foreground mb-4 line-clamp-3\">{article.excerpt}</p>\n <div className=\"flex items-center justify-between text-sm text-muted-foreground border-t pt-4\">\n <div className=\"flex items-center gap-3\">\n {article.date && (\n <div className=\"flex items-center gap-2\">\n <Calendar className=\"h-3 w-3\" />\n <span>{article.date}</span>\n </div>\n )}\n <div className=\"flex items-center gap-2\">\n <Clock className=\"h-3 w-3\" />\n <span>{article.readTime}</span>\n </div>\n </div>\n <Link\n href={`/articles/${article.slug}`}\n className=\"inline-flex items-center justify-center h-8 w-8 p-0 rounded-md hover:bg-accent transition-colors\"\n aria-label={`Read ${article.title}`}\n >\n <ArrowRight className=\"h-4 w-4\" />\n </Link>\n </div>\n </div>\n </div>\n )\n}\n","/** Keys for each renderable section of the articles listing page. */\nexport type ArticlesSection =\n | 'hero'\n | 'search'\n | 'featured'\n | 'latest'\n | 'categories'\n | 'newsletter'\n\n/**\n * CSS custom-property overrides applied to the library's wrapper div.\n * All fields are optional; omitted fields fall back to the consuming app's Tailwind theme.\n */\nexport interface ArticlesTheme {\n /** Font family for the articles section. Example: `\"'Inter', sans-serif\"` */\n fontFamily?: string\n /** Color for article card titles and section headings. Example: `'#111827'` */\n headerColor?: string\n /** Color for body and excerpt text. Example: `'#6b7280'` */\n textColor?: string\n /** Background color for sections and cards. Example: `'#ffffff'` */\n backgroundColor?: string\n /** Color for \"Read More\" links and inline links. Example: `'#4f46e5'` */\n linkColor?: string\n /** Hover color for links. Example: `'#4338ca'` */\n linkHoverColor?: string\n /** Text decoration for links. Values: `'underline'` | `'none'` */\n linkTextDecoration?: string\n /** Font weight for headings. Example: `700` */\n headerFontWeight?: string | number\n /** Font size for article card titles. Example: `'1.25rem'` */\n headerFontSize?: string\n /** Font size for body text. Example: `'1rem'` */\n bodyFontSize?: string\n /** Line height for body text. Example: `'1.75'` */\n lineHeight?: string\n /** Border radius for cards. Example: `'0.5rem'` */\n borderRadius?: string\n}\n\n/** Per-category text used in meta descriptions and on the category hero page. */\nexport interface CategoryDescription {\n /** Short description — used in meta description and category grid card. */\n short: string\n /** Long description — rendered as a hero paragraph on the category page. */\n long?: string\n}\n\n/** Controls the comments feature. Attach to `ArticlesConfig.comments`. */\nexport interface CommentsConfig {\n /** Set to `true` to enable comments globally across all articles. */\n enabled: boolean\n /** Maximum reply nesting depth. Default: `1` (replies to top-level only). */\n maxDepth?: number\n /**\n * Per-article overrides keyed by slug.\n * Example: `{ 'sensitive-article': false }` disables comments on that article only.\n */\n perArticleOverride?: Record<string, boolean>\n}\n\n/** Top-level configuration object. Pass one instance to every library component. */\nexport interface ArticlesConfig {\n /** Canonical base URL of the site, used in metadata and JSON-LD. Example: `'https://yoursite.com'` */\n siteUrl: string\n /** Site name shown in metadata titles and JSON-LD publisher fields. Example: `'Vox Populus'` */\n siteName: string\n /** Number of articles shown per page in the grid and loaded on each \"Load more\" click. Default: `6` */\n pageSize?: number\n /** Number of category cards shown before a \"Load more categories\" button appears. Default: `8` */\n categoriesPageSize?: number\n /**\n * Ordered list of sections to render on the articles listing page.\n * Omit a key to hide that section entirely. Default: `['hero','search','featured','latest','categories']`\n */\n layout?: ArticlesSection[]\n /** CSS custom-property overrides for colors, fonts, and spacing. All fields optional. */\n theme?: ArticlesTheme\n /**\n * Descriptive text for each category, keyed by category slug.\n * A plain string is treated as the short description only.\n * Example: `{ 'campaigns': { short: 'Campaign tips', long: 'Full paragraph...' } }`\n */\n categoryDescriptions?: Record<string, string | CategoryDescription>\n /** Comments configuration. Omit or set `enabled: false` to hide comments entirely. */\n comments?: CommentsConfig\n}\n\nexport const DEFAULT_PAGE_SIZE = 6\nexport const DEFAULT_CATEGORIES_PAGE_SIZE = 8\n\nexport const DEFAULT_LAYOUT: ArticlesSection[] = [\n 'hero',\n 'search',\n 'featured',\n 'latest',\n 'categories',\n]\n","'use client'\n\nimport { useState, useCallback, useEffect, useRef } from 'react'\nimport { Article, CategoryInfo } from './articleTypes'\n\nexport interface UseArticlesReturn {\n articles: Article[]\n categories: CategoryInfo[]\n searchQuery: string\n loading: boolean\n error: string | null\n handleSearch: (query: string) => void\n}\n\nexport function useArticles(): UseArticlesReturn {\n const [searchQuery, setSearchQuery] = useState('')\n const debounceRef = useRef<ReturnType<typeof setTimeout> | null>(null)\n const [articles, setArticles] = useState<Article[]>([])\n const [categories, setCategories] = useState<CategoryInfo[]>([])\n const [loading, setLoading] = useState(true)\n const [error, setError] = useState<string | null>(null)\n\n const fetchArticles = useCallback(async (query: string) => {\n try {\n setLoading(true)\n const url = query ? `/api/articles?q=${encodeURIComponent(query)}` : '/api/articles'\n const response = await fetch(url)\n\n if (!response.ok) throw new Error('Failed to fetch articles')\n\n const data = await response.json()\n setArticles(data.articles)\n\n if (!query?.trim()) {\n const catMap = new Map<string, { count: number; featuredImage: string }>()\n for (const article of data.articles as Article[]) {\n for (const cat of article.categories) {\n if (!catMap.has(cat)) {\n catMap.set(cat, { count: 0, featuredImage: article.featuredImage })\n }\n catMap.get(cat)!.count++\n }\n }\n setCategories(\n Array.from(catMap.entries())\n .map(([name, { count, featuredImage }]) => ({\n name,\n slug: name\n .toLowerCase()\n .replaceAll(/\\s+/g, '-')\n .replaceAll(/[^a-z0-9-]/g, ''),\n count,\n featuredImage,\n }))\n .sort((a, b) => b.count - a.count)\n )\n }\n\n setError(null)\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to load articles')\n setArticles([])\n } finally {\n setLoading(false)\n }\n }, [])\n\n useEffect(() => {\n fetchArticles('')\n }, [fetchArticles])\n\n const handleSearch = useCallback(\n (query: string) => {\n setSearchQuery(query)\n if (debounceRef.current) clearTimeout(debounceRef.current)\n if (query === '') {\n fetchArticles('')\n } else if (query.length >= 3) {\n debounceRef.current = setTimeout(() => fetchArticles(query), 1000)\n }\n },\n [fetchArticles]\n )\n\n useEffect(\n () => () => {\n if (debounceRef.current) clearTimeout(debounceRef.current)\n },\n []\n )\n\n return { articles, categories, searchQuery, loading, error, handleSearch }\n}\n","'use client'\n\nimport React from 'react'\nimport type { Article, CategoryInfo } from './articleTypes'\nimport { ArticleCategoryGrid } from './ArticleCategoryGrid'\nimport { CollectionPageSchema } from './ArticleSchemas'\nimport { ArticleSearchBar } from './ArticleSearchBar'\nimport { ArticlesHero } from './ArticlesHero'\nimport { FeaturedArticle } from './FeaturedArticle'\nimport { LatestArticlesSection } from './LatestArticlesSection'\nimport {\n ArticlesConfig,\n ArticlesSection,\n ArticlesTheme,\n DEFAULT_CATEGORIES_PAGE_SIZE,\n DEFAULT_LAYOUT,\n DEFAULT_PAGE_SIZE,\n} from './articlesConfig'\nimport { useArticles } from './useArticles'\n\ninterface SectionContext {\n articles: Article[]\n displayedArticles: Article[]\n categories: CategoryInfo[]\n searchQuery: string\n loading: boolean\n error: string | null\n handleSearch: (query: string) => void\n pageSize: number\n categoriesPageSize: number\n}\n\nfunction buildThemeVars(theme?: ArticlesTheme): React.CSSProperties {\n if (!theme) return {}\n const vars: Record<string, string> = {}\n if (theme.fontFamily) vars['--articles-font-family'] = theme.fontFamily\n if (theme.headerColor) vars['--articles-header-color'] = theme.headerColor\n if (theme.textColor) vars['--articles-text-color'] = theme.textColor\n if (theme.backgroundColor) vars['--articles-bg-color'] = theme.backgroundColor\n if (theme.linkColor) vars['--articles-link-color'] = theme.linkColor\n if (theme.linkHoverColor) vars['--articles-link-hover-color'] = theme.linkHoverColor\n if (theme.linkTextDecoration) vars['--articles-link-decoration'] = theme.linkTextDecoration\n if (theme.headerFontWeight) vars['--articles-header-font-weight'] = String(theme.headerFontWeight)\n if (theme.headerFontSize) vars['--articles-header-font-size'] = theme.headerFontSize\n if (theme.bodyFontSize) vars['--articles-body-font-size'] = theme.bodyFontSize\n if (theme.lineHeight) vars['--articles-line-height'] = theme.lineHeight\n if (theme.borderRadius) vars['--articles-border-radius'] = theme.borderRadius\n return vars as React.CSSProperties\n}\n\nfunction renderSection(section: ArticlesSection, ctx: SectionContext): React.ReactNode {\n const {\n articles,\n displayedArticles,\n categories,\n searchQuery,\n loading,\n error,\n handleSearch,\n pageSize,\n categoriesPageSize,\n } = ctx\n\n switch (section) {\n case 'hero':\n return <ArticlesHero key=\"hero\" />\n\n case 'search':\n return (\n <ArticleSearchBar\n key=\"search\"\n value={searchQuery}\n onChange={handleSearch}\n disabled={loading}\n resultCount={articles.length}\n />\n )\n\n case 'featured':\n return articles.length > 0 && !searchQuery ? (\n <FeaturedArticle key=\"featured\" article={articles[0]} />\n ) : null\n\n case 'latest':\n if (loading && articles.length === 0) {\n return (\n <section key=\"latest\" id=\"latest\" className=\"py-16 bg-muted/50 flex-1\">\n <div className=\"max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 flex items-center justify-center py-24\">\n <div className=\"text-center\">\n <div className=\"animate-spin rounded-full h-12 w-12 border-b-2 border-primary mx-auto mb-4\" />\n <p className=\"text-muted-foreground\">Loading articles...</p>\n </div>\n </div>\n </section>\n )\n }\n if (error) {\n return (\n <section key=\"latest\" id=\"latest\" className=\"py-16 bg-muted/50 flex-1\">\n <div className=\"max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 flex items-center justify-center\">\n <div className=\"bg-red-50 border border-red-200 rounded-lg p-6 max-w-md text-center\">\n <h3 className=\"text-lg font-semibold text-red-800 mb-2\">Error Loading Articles</h3>\n <p className=\"text-red-600 mb-4\">{error}</p>\n <button\n type=\"button\"\n onClick={() => handleSearch('')}\n className=\"inline-flex items-center justify-center px-4 py-2 border border-input bg-background hover:bg-accent rounded-md font-medium text-sm transition-colors\"\n >\n Try again\n </button>\n </div>\n </div>\n </section>\n )\n }\n return (\n <LatestArticlesSection\n key=\"latest\"\n articles={displayedArticles}\n searchQuery={searchQuery}\n onClearSearch={() => handleSearch('')}\n pageSize={pageSize}\n />\n )\n\n case 'categories':\n return searchQuery ? null : (\n <ArticleCategoryGrid\n key=\"categories\"\n categories={categories}\n pageSize={categoriesPageSize}\n />\n )\n\n // 'newsletter' is intentionally omitted — inject it via the layout prop in the consuming app\n default:\n return null\n }\n}\n\nexport function ArticlesPage({ config }: Readonly<{ config: ArticlesConfig }>) {\n const state = useArticles()\n const layout = config.layout ?? DEFAULT_LAYOUT\n const pageSize = config.pageSize ?? DEFAULT_PAGE_SIZE\n const categoriesPageSize = config.categoriesPageSize ?? DEFAULT_CATEGORIES_PAGE_SIZE\n const themeVars = buildThemeVars(config.theme)\n\n const hasFeatured = layout.includes('featured')\n const articlesWithoutFeatured = hasFeatured ? state.articles.slice(1) : state.articles\n const displayedArticles = state.searchQuery ? state.articles : articlesWithoutFeatured\n\n const ctx: SectionContext = { ...state, displayedArticles, pageSize, categoriesPageSize }\n\n return (\n <div style={themeVars}>\n {layout.map((section) => renderSection(section, ctx))}\n {state.articles.length > 0 && (\n <CollectionPageSchema\n title={`Articles | ${config.siteName}`}\n description={`Browse all ${state.articles.length} articles on ${config.siteName}.`}\n url={`${config.siteUrl}/articles`}\n articleCount={state.articles.length}\n />\n )}\n </div>\n )\n}\n","'use client'\n\nimport Image from 'next/image'\nimport Link from 'next/link'\nimport { BreadcrumbSchema } from './ArticleSchemas'\nimport { LatestArticles } from './LatestArticles'\nimport { ArticlesConfig, DEFAULT_PAGE_SIZE } from './articlesConfig'\nimport type { Article } from './articleTypes'\n\nfunction getCategoryDescription(\n config: ArticlesConfig,\n slug: string,\n categoryName: string\n): { short: string; long?: string } {\n const raw = config.categoryDescriptions?.[slug]\n if (!raw) return { short: `Browse all articles in ${categoryName}.` }\n if (typeof raw === 'string') return { short: raw }\n return raw\n}\n\ntype CategoryArticlesPageProps = Readonly<{\n category: string\n articles: Article[]\n config: ArticlesConfig\n}>\n\nexport function CategoryArticlesPage({ category, articles, config }: CategoryArticlesPageProps) {\n if (articles.length === 0) return null\n\n const categoryName = articles[0].category\n const heroImage = articles[0].featuredImage\n const description = getCategoryDescription(config, category, categoryName)\n const pageSize = config.pageSize ?? DEFAULT_PAGE_SIZE\n const siteUrl = config.siteUrl.replace(/\\/$/, '')\n\n return (\n <div>\n <BreadcrumbSchema\n items={[\n { name: 'Home', url: siteUrl },\n { name: 'Articles', url: `${siteUrl}/articles` },\n { name: categoryName, url: `${siteUrl}/articles/category/${category}` },\n ]}\n />\n {/* Hero */}\n <section className=\"relative h-72 md:h-96 flex items-center justify-center overflow-hidden\">\n <Image\n src={heroImage}\n alt={categoryName}\n fill\n sizes=\"100vw\"\n className=\"object-cover\"\n priority\n />\n <div className=\"absolute inset-0 bg-black/60\" />\n <div className=\"relative z-10 text-center text-white px-4\">\n <p className=\"text-sm font-semibold uppercase tracking-widest mb-3 opacity-80\">\n Category\n </p>\n <h1 className=\"text-4xl md:text-5xl font-bold mb-3\">{categoryName}</h1>\n {description.long && (\n <p className=\"text-lg opacity-90 max-w-2xl mx-auto mt-2\">{description.long}</p>\n )}\n <p className=\"text-lg opacity-70 mt-2\">\n {articles.length} article{articles.length === 1 ? '' : 's'}\n </p>\n </div>\n </section>\n\n {/* Articles */}\n <section className=\"py-16 bg-muted/50 flex-1\">\n <div className=\"max-w-7xl mx-auto px-4 sm:px-6 lg:px-8\">\n <div className=\"mb-8 flex items-center justify-between\">\n <Link href=\"/articles\" className=\"text-sm text-primary hover:underline\">\n ← All Articles\n </Link>\n <p className=\"text-sm text-muted-foreground\">{description.short}</p>\n </div>\n <LatestArticles articles={articles} pageSize={pageSize} />\n </div>\n </section>\n </div>\n )\n}\n","'use client'\n\nimport { useSession, signIn } from 'next-auth/react'\nimport { useCallback, useEffect, useState } from 'react'\nimport { CommentForm } from './CommentForm'\nimport { CommentThread } from './CommentThread'\nimport type { CommentsConfig } from './articlesConfig'\nimport type { ArticleCommentWithReplies } from './commentTypes'\n\ninterface CommentsSectionProps {\n readonly articleSlug: string\n readonly config: CommentsConfig\n}\n\nexport function CommentsSection({ articleSlug, config }: CommentsSectionProps) {\n const { data: session, status } = useSession()\n const [comments, setComments] = useState<ArticleCommentWithReplies[]>([])\n const [loading, setLoading] = useState(true)\n const [fetchError, setFetchError] = useState<string | null>(null)\n\n const perArticleEnabled = config.perArticleOverride?.[articleSlug] ?? true\n const enabled = config.enabled && perArticleEnabled\n\n const currentUserId = session?.user?.email ?? session?.user?.name ?? undefined\n\n const fetchComments = useCallback(async () => {\n setLoading(true)\n setFetchError(null)\n try {\n const res = await fetch(`/api/articles/${articleSlug}/comments`)\n if (!res.ok) throw new Error('Failed to load comments')\n const data = (await res.json()) as { comments: ArticleCommentWithReplies[] }\n setComments(data.comments)\n } catch {\n setFetchError('Could not load comments. Please try again.')\n } finally {\n setLoading(false)\n }\n }, [articleSlug])\n\n useEffect(() => {\n if (enabled) {\n fetchComments().catch(() => undefined)\n }\n }, [fetchComments, enabled])\n\n if (!enabled) return null\n\n const count = comments.length\n const label = count === 1 ? '1 comment' : `${count} comments`\n\n return (\n <section aria-label=\"Comments\" className=\"mt-12 pt-8 border-t border-border space-y-6\">\n <h2 className=\"text-xl font-semibold text-foreground\">{loading ? 'Comments' : label}</h2>\n\n {status === 'authenticated' ? (\n <CommentForm articleSlug={articleSlug} onSuccess={fetchComments} />\n ) : (\n <p className=\"text-sm text-muted-foreground\">\n <button\n onClick={() => signIn()}\n className=\"text-primary underline hover:no-underline focus:outline-none\"\n >\n Sign in\n </button>{' '}\n to join the discussion.\n </p>\n )}\n\n {fetchError && <p className=\"text-sm text-destructive\">{fetchError}</p>}\n\n {loading ? (\n <p className=\"text-sm text-muted-foreground\">Loading comments…</p>\n ) : (\n <CommentThread\n comments={comments}\n articleSlug={articleSlug}\n currentUserId={currentUserId}\n onRefresh={fetchComments}\n />\n )}\n </section>\n )\n}\n","'use client'\n\nimport { useState } from 'react'\n\nconst MAX_LENGTH = 2000\nconst WARN_THRESHOLD = 1800\n\nfunction submitLabel(submitting: boolean, parentId?: string): string {\n if (submitting) return 'Posting…'\n return parentId ? 'Reply' : 'Post comment'\n}\n\ninterface CommentFormProps {\n readonly articleSlug: string\n readonly parentId?: string\n readonly onSuccess: () => void\n readonly onCancel?: () => void\n}\n\nexport function CommentForm({ articleSlug, parentId, onSuccess, onCancel }: CommentFormProps) {\n const [body, setBody] = useState('')\n const [submitting, setSubmitting] = useState(false)\n const [error, setError] = useState<string | null>(null)\n\n const remaining = MAX_LENGTH - body.length\n\n async function handleSubmit(e: React.FormEvent) {\n e.preventDefault()\n if (!body.trim() || submitting) return\n\n setSubmitting(true)\n setError(null)\n\n try {\n const res = await fetch(`/api/articles/${articleSlug}/comments`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ body: body.trim(), parentId: parentId ?? null }),\n })\n\n if (!res.ok) {\n const data = (await res.json()) as { error?: string }\n setError(data.error ?? 'Failed to post comment')\n return\n }\n\n setBody('')\n onSuccess()\n } catch {\n setError('Network error — please try again')\n } finally {\n setSubmitting(false)\n }\n }\n\n return (\n <form onSubmit={handleSubmit} className=\"space-y-2\">\n <textarea\n value={body}\n onChange={(e) => setBody(e.target.value)}\n maxLength={MAX_LENGTH}\n rows={parentId ? 3 : 4}\n placeholder={parentId ? 'Write a reply…' : 'Join the discussion…'}\n disabled={submitting}\n className=\"w-full rounded-md border border-border bg-background px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-primary/50 disabled:opacity-50 resize-none\"\n aria-label={parentId ? 'Reply text' : 'Comment text'}\n />\n\n {remaining <= MAX_LENGTH - WARN_THRESHOLD && (\n <p className={`text-xs ${remaining < 0 ? 'text-destructive' : 'text-muted-foreground'}`}>\n {remaining} characters remaining\n </p>\n )}\n\n {error && <p className=\"text-xs text-destructive\">{error}</p>}\n\n <div className=\"flex gap-2\">\n <button\n type=\"submit\"\n disabled={submitting || body.trim().length === 0 || body.length > MAX_LENGTH}\n className=\"rounded-md bg-primary px-4 py-1.5 text-sm font-medium text-primary-foreground hover:bg-primary/90 disabled:opacity-50 disabled:cursor-not-allowed\"\n >\n {submitLabel(submitting, parentId)}\n </button>\n\n {onCancel && (\n <button\n type=\"button\"\n onClick={onCancel}\n className=\"rounded-md px-4 py-1.5 text-sm font-medium text-muted-foreground hover:text-foreground\"\n >\n Cancel\n </button>\n )}\n </div>\n </form>\n )\n}\n","'use client'\n\nimport { useState } from 'react'\nimport { CommentForm } from './CommentForm'\nimport type { ArticleComment, ArticleCommentWithReplies } from './commentTypes'\n\nfunction relativeTime(iso: string): string {\n const diff = Date.now() - new Date(iso).getTime()\n const minutes = Math.floor(diff / 60_000)\n if (minutes < 1) return 'just now'\n if (minutes < 60) return `${minutes}m ago`\n const hours = Math.floor(minutes / 60)\n if (hours < 24) return `${hours}h ago`\n const days = Math.floor(hours / 24)\n if (days < 30) return `${days}d ago`\n const months = Math.floor(days / 30)\n if (months < 12) return `${months}mo ago`\n return `${Math.floor(months / 12)}y ago`\n}\n\ninterface CommentItemProps {\n readonly comment: ArticleCommentWithReplies | ArticleComment\n readonly articleSlug: string\n readonly currentUserId?: string\n readonly isReply?: boolean\n readonly onRefresh: () => void\n}\n\nexport function CommentItem({\n comment,\n articleSlug,\n currentUserId,\n isReply = false,\n onRefresh,\n}: CommentItemProps) {\n const [showReplyForm, setShowReplyForm] = useState(false)\n const [deleting, setDeleting] = useState(false)\n\n const replies = 'replies' in comment ? comment.replies : []\n const canReply = !isReply && !!currentUserId && !comment.isDeleted\n const canDelete = !!currentUserId && currentUserId === comment.authorId && !comment.isDeleted\n\n async function handleDelete() {\n if (deleting) return\n setDeleting(true)\n try {\n await fetch(`/api/articles/${articleSlug}/comments/${comment.id}`, { method: 'DELETE' })\n onRefresh()\n } finally {\n setDeleting(false)\n }\n }\n\n return (\n <div className={`${isReply ? 'ml-8 border-l-2 border-border pl-4' : ''}`}>\n <div className=\"rounded-lg bg-muted/40 p-3 space-y-1\">\n {comment.isDeleted ? (\n <p className=\"text-sm italic text-muted-foreground\">Comment removed</p>\n ) : (\n <>\n <div className=\"flex items-center justify-between gap-2\">\n <span className=\"text-sm font-medium text-foreground\">{comment.authorName}</span>\n <span className=\"text-xs text-muted-foreground\">\n {relativeTime(comment.createdAt)}\n </span>\n </div>\n <p className=\"text-sm text-foreground whitespace-pre-wrap break-words\">\n {comment.body}\n </p>\n <div className=\"flex gap-3 pt-1\">\n {canReply && (\n <button\n onClick={() => setShowReplyForm((v) => !v)}\n className=\"text-xs text-muted-foreground hover:text-foreground\"\n >\n {showReplyForm ? 'Cancel' : 'Reply'}\n </button>\n )}\n {canDelete && (\n <button\n onClick={handleDelete}\n disabled={deleting}\n className=\"text-xs text-muted-foreground hover:text-destructive disabled:opacity-50\"\n >\n {deleting ? 'Deleting…' : 'Delete'}\n </button>\n )}\n </div>\n </>\n )}\n </div>\n\n {showReplyForm && (\n <div className=\"mt-2 ml-2\">\n <CommentForm\n articleSlug={articleSlug}\n parentId={comment.id}\n onSuccess={() => {\n setShowReplyForm(false)\n onRefresh()\n }}\n onCancel={() => setShowReplyForm(false)}\n />\n </div>\n )}\n\n {replies.length > 0 && (\n <div className=\"mt-2 space-y-2\">\n {replies.map((reply) => (\n <CommentItem\n key={reply.id}\n comment={{ ...reply, replies: [] }}\n articleSlug={articleSlug}\n currentUserId={currentUserId}\n isReply\n onRefresh={onRefresh}\n />\n ))}\n </div>\n )}\n </div>\n )\n}\n","'use client'\n\nimport { CommentItem } from './CommentItem'\nimport type { ArticleCommentWithReplies } from './commentTypes'\n\ninterface CommentThreadProps {\n readonly comments: ArticleCommentWithReplies[]\n readonly articleSlug: string\n readonly currentUserId?: string\n readonly onRefresh: () => void\n}\n\nexport function CommentThread({\n comments,\n articleSlug,\n currentUserId,\n onRefresh,\n}: CommentThreadProps) {\n if (comments.length === 0) {\n return (\n <p className=\"text-sm text-muted-foreground text-center py-6\">\n No comments yet. Be the first to share your thoughts.\n </p>\n )\n }\n\n return (\n <div className=\"space-y-4\">\n {comments.map((comment) => (\n <CommentItem\n key={comment.id}\n comment={comment}\n articleSlug={articleSlug}\n currentUserId={currentUserId}\n onRefresh={onRefresh}\n />\n ))}\n </div>\n )\n}\n"],"mappings":"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","names":["jsx","jsx","jsxs","Link","jsx","jsxs","Link","Image","jsx","jsxs","Search","useState","useEffect","Image","Link","ArrowRight","Calendar","Clock","jsx","jsxs","jsx","jsxs","useState","useEffect","jsx","jsxs","Search","useState","useEffect","useState","useEffect","jsx","jsxs","Image","Link","jsx","jsxs","Image","Link","useCallback","useEffect","useState","useState","jsx","jsxs","useState","e","useState","jsx","jsxs","useState","jsx","jsx","jsxs","useState","useCallback","useEffect"]}
1
+ {"version":3,"sources":["../src/ArticleCategoryGrid.tsx","../src/ArticleSchemas.tsx","../src/ArticleSearchBar.tsx","../src/ArticlesHero.tsx","../src/FeaturedArticle.tsx","../src/LatestArticlesSection.tsx","../src/LatestArticles.tsx","../src/ArticleCard.tsx","../src/articlesConfig.ts","../src/useArticles.ts","../src/ArticlesPage.tsx","../src/CategoryArticlesPage.tsx","../src/CommentsSection.tsx","../src/CommentForm.tsx","../src/CommentItem.tsx","../src/CommentThread.tsx"],"sourcesContent":["'use client'\n\nimport Image from 'next/image'\nimport Link from 'next/link'\nimport { useState, useEffect } from 'react'\nimport type { CategoryInfo } from './articleTypes'\n\ntype ArticleCategoryGridProps = Readonly<{\n categories: CategoryInfo[]\n pageSize?: number\n}>\n\nexport function ArticleCategoryGrid({ categories, pageSize = 8 }: ArticleCategoryGridProps) {\n const [visibleCount, setVisibleCount] = useState(pageSize)\n\n useEffect(() => {\n setVisibleCount(pageSize)\n }, [categories, pageSize])\n\n if (categories.length === 0) return null\n\n const visible = categories.slice(0, visibleCount)\n const hasMore = visibleCount < categories.length\n\n return (\n <section className=\"py-16 bg-background\">\n <div className=\"max-w-7xl mx-auto px-4 sm:px-6 lg:px-8\">\n <div className=\"text-center mb-12\">\n <h2 className=\"text-3xl font-bold text-foreground mb-4\">Browse by Category</h2>\n <p className=\"text-lg text-muted-foreground\">\n Explore our articles by topic to find the insights most relevant to your interests.\n </p>\n </div>\n\n <div\n className=\"sm:grid-cols-4\"\n style={{\n display: 'grid',\n gridTemplateColumns: 'repeat(1, minmax(0, 1fr))',\n gap: '1.5rem',\n }}\n >\n {visible.map((cat) => (\n <Link key={cat.slug} href={`/articles/category/${cat.slug}`}>\n <div className=\"group overflow-hidden rounded-xl border border-border shadow-sm hover:shadow-lg transition-all duration-300 cursor-pointer bg-card\">\n <div className=\"overflow-hidden\" style={{ position: 'relative', height: '13rem' }}>\n <Image\n src={cat.featuredImage}\n alt={cat.name}\n fill\n sizes=\"(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw\"\n className=\"object-cover group-hover:scale-105 transition-transform duration-300\"\n />\n </div>\n <div className=\"p-4 text-center\">\n <h3 className=\"font-semibold text-foreground text-base\">{cat.name}</h3>\n <p className=\"text-sm text-muted-foreground mt-1\">\n {cat.count} {cat.count === 1 ? 'article' : 'articles'}\n </p>\n </div>\n </div>\n </Link>\n ))}\n </div>\n\n {hasMore && (\n <div className=\"mt-10 text-center\">\n <button\n type=\"button\"\n onClick={() => setVisibleCount((c) => c + pageSize)}\n className=\"inline-flex items-center justify-center px-4 py-2 border border-input bg-background hover:bg-accent rounded-md font-medium text-sm transition-colors\"\n >\n Load more categories\n </button>\n </div>\n )}\n </div>\n </section>\n )\n}\n","import { Article } from './articleTypes'\n\ntype ArticleSchemaProps = Readonly<{\n article: Article\n articleUrl: string\n siteName: string\n}>\n\nexport function ArticleSchema({ article, articleUrl, siteName }: ArticleSchemaProps) {\n const schema = {\n '@context': 'https://schema.org',\n '@type': 'Article',\n headline: article.title,\n description: article.excerpt,\n image: article.featuredImage,\n ...(article.date && { datePublished: new Date(article.date).toISOString() }),\n author: { '@type': 'Person', name: article.author },\n publisher: { '@type': 'Organization', name: siteName },\n mainEntityOfPage: { '@type': 'WebPage', '@id': articleUrl },\n }\n\n return (\n <script\n type=\"application/ld+json\"\n dangerouslySetInnerHTML={{ __html: JSON.stringify(schema) }}\n />\n )\n}\n\ntype BreadcrumbSchemaProps = Readonly<{\n items: ReadonlyArray<{ name: string; url: string }>\n}>\n\nexport function BreadcrumbSchema({ items }: BreadcrumbSchemaProps) {\n const schema = {\n '@context': 'https://schema.org',\n '@type': 'BreadcrumbList',\n itemListElement: items.map((item, index) => ({\n '@type': 'ListItem',\n position: index + 1,\n name: item.name,\n item: item.url,\n })),\n }\n\n return (\n <script\n type=\"application/ld+json\"\n dangerouslySetInnerHTML={{ __html: JSON.stringify(schema) }}\n />\n )\n}\n\ntype CollectionPageSchemaProps = Readonly<{\n title: string\n description: string\n url: string\n articleCount: number\n}>\n\nexport function CollectionPageSchema({\n title,\n description,\n url,\n articleCount,\n}: CollectionPageSchemaProps) {\n const schema = {\n '@context': 'https://schema.org',\n '@type': 'CollectionPage',\n name: title,\n description,\n url,\n numberOfItems: articleCount,\n }\n\n return (\n <script\n type=\"application/ld+json\"\n dangerouslySetInnerHTML={{ __html: JSON.stringify(schema) }}\n />\n )\n}\n","import { Search, X } from 'lucide-react'\n\ntype ArticleSearchBarProps = Readonly<{\n value: string\n onChange: (query: string) => void\n disabled?: boolean\n resultCount?: number\n}>\n\nexport function ArticleSearchBar({\n value,\n onChange,\n disabled,\n resultCount,\n}: ArticleSearchBarProps) {\n return (\n <section className=\"py-8 bg-background border-b border-border\">\n <div className=\"max-w-4xl mx-auto px-4 sm:px-6 lg:px-8\">\n <div className=\"relative\">\n <Search className=\"absolute left-3 top-1/2 transform -translate-y-1/2 text-muted-foreground h-5 w-5\" />\n <input\n type=\"text\"\n placeholder=\"Search articles (3+ characters required)...\"\n value={value}\n onChange={(e) => onChange(e.target.value)}\n disabled={disabled}\n className=\"flex w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 pl-10 pr-10 py-3 text-lg\"\n />\n {value && (\n <button\n type=\"button\"\n onClick={() => onChange('')}\n aria-label=\"Clear search\"\n className=\"absolute right-3 top-1/2 transform -translate-y-1/2 text-muted-foreground hover:text-foreground\"\n >\n <X className=\"h-5 w-5\" />\n </button>\n )}\n </div>\n {value && (\n <div className=\"mt-2 text-sm text-muted-foreground\">\n {resultCount === 0\n ? 'No articles found matching your search.'\n : `Found ${resultCount} article${resultCount === 1 ? '' : 's'} matching \"${value}\"`}\n </div>\n )}\n </div>\n </section>\n )\n}\n","import Link from 'next/link'\n\nconst DEFAULT_TITLE = 'Vox Populus Insights'\nconst DEFAULT_DESCRIPTION =\n 'Expert analysis, campaign strategies, and voter engagement insights from the frontlines of democracy.'\n\ntype ArticlesHeroProps = Readonly<{\n title?: string\n description?: string\n}>\n\nexport function ArticlesHero({\n title = DEFAULT_TITLE,\n description = DEFAULT_DESCRIPTION,\n}: ArticlesHeroProps) {\n return (\n <section className=\"bg-linear-to-r from-gray-50 to-gray-100 py-16 md:py-24\">\n <div className=\"max-w-7xl mx-auto px-4 sm:px-6 lg:px-8\">\n <div className=\"max-w-3xl mx-auto text-center\">\n <h1 className=\"text-4xl md:text-5xl font-bold text-foreground mb-6\">{title}</h1>\n <p className=\"text-xl text-muted-foreground mb-8\">{description}</p>\n <div className=\"flex flex-col sm:flex-row gap-4 justify-center\">\n <Link\n href=\"#latest\"\n className=\"inline-flex items-center justify-center px-4 py-2 bg-primary hover:bg-primary/90 text-primary-foreground rounded-md font-medium text-sm transition-colors\"\n >\n Browse Latest Articles\n </Link>\n <Link\n href=\"/contact\"\n className=\"inline-flex items-center justify-center px-4 py-2 border border-primary text-primary hover:bg-primary/10 rounded-md font-medium text-sm transition-colors\"\n >\n Contact Our Team\n </Link>\n </div>\n </div>\n </div>\n </section>\n )\n}\n","import Link from 'next/link'\nimport Image from 'next/image'\nimport { ArrowRight, Calendar, Clock, User } from 'lucide-react'\nimport type { Article } from './articleTypes'\n\ntype FeaturedArticleProps = Readonly<{\n article: Article\n}>\n\nexport function FeaturedArticle({ article }: FeaturedArticleProps) {\n return (\n <section className=\"py-16 bg-background\">\n <div className=\"max-w-7xl mx-auto px-4 sm:px-6 lg:px-8\">\n <div className=\"grid lg:grid-cols-2 gap-12 items-center\">\n <div>\n <span className=\"inline-block bg-primary/10 text-primary text-sm font-medium px-3 py-1 rounded-full mb-4\">\n FEATURED ARTICLE\n </span>\n <h2 className=\"text-3xl font-bold text-foreground mb-4\">{article.title}</h2>\n <p className=\"text-lg text-muted-foreground mb-6\">{article.excerpt}</p>\n <div className=\"flex items-center gap-6 text-sm text-muted-foreground mb-6\">\n {article.date && (\n <div className=\"flex items-center gap-2\">\n <Calendar className=\"h-4 w-4\" />\n <span>{article.date}</span>\n </div>\n )}\n <div className=\"flex items-center gap-2\">\n <Clock className=\"h-4 w-4\" />\n <span>{article.readTime}</span>\n </div>\n <div className=\"flex items-center gap-2\">\n <User className=\"h-4 w-4\" />\n <span>{article.author}</span>\n </div>\n </div>\n <Link\n href={`/articles/${article.slug}`}\n className=\"inline-flex items-center justify-center px-4 py-2 bg-primary hover:bg-primary/90 text-primary-foreground rounded-md font-medium text-sm transition-colors\"\n >\n Read Full Article\n <ArrowRight className=\"ml-2 h-4 w-4\" />\n </Link>\n </div>\n <div className=\"bg-card rounded-lg p-8\">\n <Image\n src={article.featuredImage}\n alt={article.title}\n width={800}\n height={450}\n className=\"w-full h-64 object-cover rounded-lg mb-4\"\n />\n <div className=\"flex gap-2\">\n <span className=\"bg-primary/10 text-primary text-xs font-medium px-2 py-1 rounded\">\n {article.category}\n </span>\n </div>\n </div>\n </div>\n </div>\n </section>\n )\n}\n","'use client'\n\nimport { Search } from 'lucide-react'\nimport type { Article } from './articleTypes'\nimport { LatestArticles } from './LatestArticles'\n\ninterface LatestArticlesSectionProps {\n readonly articles: Article[]\n readonly searchQuery: string\n readonly onClearSearch: () => void\n readonly pageSize?: number\n}\n\nfunction DescriptionText({\n searchQuery,\n count,\n}: {\n readonly searchQuery: string\n readonly count: number\n}) {\n const pluralSuffix = count === 1 ? '' : 's'\n const text = searchQuery\n ? `Found ${count} article${pluralSuffix} matching your search.`\n : 'Stay informed with our latest insights on voter engagement, campaign strategies, and civic technology.'\n return <p className=\"text-lg text-muted-foreground\">{text}</p>\n}\n\nexport function LatestArticlesSection({\n articles,\n searchQuery,\n onClearSearch,\n pageSize = 6,\n}: Readonly<LatestArticlesSectionProps>) {\n return (\n <section id=\"latest\" className=\"py-16 bg-muted/50\">\n <div className=\"max-w-7xl mx-auto px-4 sm:px-6 lg:px-8\">\n <div className=\"text-center mb-12\">\n <h2 className=\"text-3xl font-bold text-foreground mb-4\">\n {searchQuery ? 'Search Results' : 'Latest Articles'}\n </h2>\n <DescriptionText searchQuery={searchQuery} count={articles.length} />\n </div>\n\n {articles.length === 0 && searchQuery ? (\n <div className=\"text-center py-12\">\n <div className=\"max-w-md mx-auto\">\n <Search className=\"h-12 w-12 text-muted-foreground mx-auto mb-4\" />\n <h3 className=\"text-lg font-semibold text-foreground mb-2\">No articles found</h3>\n <p className=\"text-muted-foreground mb-4\">\n We couldn&apos;t find any articles matching &quot;{searchQuery}&quot;. Try adjusting\n your search terms.\n </p>\n <button\n type=\"button\"\n onClick={onClearSearch}\n className=\"inline-flex items-center justify-center px-4 py-2 border border-input bg-background hover:bg-accent rounded-md font-medium text-sm transition-colors\"\n >\n Clear search\n </button>\n </div>\n </div>\n ) : (\n <LatestArticles articles={articles} pageSize={pageSize} key={searchQuery} />\n )}\n </div>\n </section>\n )\n}\n","'use client'\n\nimport { useState, useEffect } from 'react'\nimport type { Article } from './articleTypes'\nimport { ArticleCard } from './ArticleCard'\n\ninterface LatestArticlesProps {\n readonly articles: Article[]\n readonly pageSize: number\n}\n\nexport function LatestArticles({ articles, pageSize }: Readonly<LatestArticlesProps>) {\n const [visibleCount, setVisibleCount] = useState(pageSize)\n\n useEffect(() => {\n setVisibleCount(pageSize)\n }, [articles, pageSize])\n\n const visible = articles.slice(0, visibleCount)\n const hasMore = visibleCount < articles.length\n\n return (\n <div>\n <div className=\"grid md:grid-cols-2 lg:grid-cols-3 gap-8\">\n {visible.map((article) => (\n <ArticleCard key={article.slug} article={article} />\n ))}\n </div>\n {hasMore && (\n <div className=\"mt-10 text-center\">\n <button\n type=\"button\"\n onClick={() => setVisibleCount((c) => c + pageSize)}\n className=\"inline-flex items-center justify-center px-4 py-2 border border-input bg-background hover:bg-accent rounded-md font-medium text-sm transition-colors\"\n >\n Load more articles\n </button>\n </div>\n )}\n </div>\n )\n}\n","'use client'\n\nimport Image from 'next/image'\nimport Link from 'next/link'\nimport { ArrowRight, Calendar, Clock } from 'lucide-react'\nimport type { Article } from './articleTypes'\n\ntype ArticleCardProps = Readonly<{\n article: Article\n}>\n\nexport function ArticleCard({ article }: ArticleCardProps) {\n return (\n <div className=\"rounded-lg border border-border bg-card text-card-foreground shadow-sm hover:shadow-lg transition-shadow duration-300\">\n <div className=\"p-0\">\n <Image\n src={article.featuredImage}\n alt={article.title}\n className=\"w-full h-48 object-cover rounded-t-lg\"\n width={400}\n height={200}\n />\n </div>\n <div className=\"p-6\">\n <div className=\"flex gap-2 mb-3\">\n <span className=\"bg-primary/10 text-primary text-xs font-medium px-2 py-1 rounded\">\n {article.category}\n </span>\n </div>\n <h3 className=\"text-lg font-semibold leading-none tracking-tight mb-2\">\n <Link\n href={`/articles/${article.slug}`}\n className=\"hover:text-indigo-600 transition-colors\"\n >\n {article.title}\n </Link>\n </h3>\n <p className=\"text-muted-foreground mb-4 line-clamp-3\">{article.excerpt}</p>\n <div className=\"flex items-center justify-between text-sm text-muted-foreground border-t pt-4\">\n <div className=\"flex items-center gap-3\">\n {article.date && (\n <div className=\"flex items-center gap-2\">\n <Calendar className=\"h-3 w-3\" />\n <span>{article.date}</span>\n </div>\n )}\n <div className=\"flex items-center gap-2\">\n <Clock className=\"h-3 w-3\" />\n <span>{article.readTime}</span>\n </div>\n </div>\n <Link\n href={`/articles/${article.slug}`}\n className=\"inline-flex items-center justify-center h-8 w-8 p-0 rounded-md hover:bg-accent transition-colors\"\n aria-label={`Read ${article.title}`}\n >\n <ArrowRight className=\"h-4 w-4\" />\n </Link>\n </div>\n </div>\n </div>\n )\n}\n","/** Keys for each renderable section of the articles listing page. */\nexport type ArticlesSection =\n | 'hero'\n | 'search'\n | 'featured'\n | 'latest'\n | 'categories'\n | 'newsletter'\n\n/**\n * CSS custom-property overrides applied to the library's wrapper div.\n * All fields are optional; omitted fields fall back to the consuming app's Tailwind theme.\n */\nexport interface ArticlesTheme {\n /** Font family for the articles section. Example: `\"'Inter', sans-serif\"` */\n fontFamily?: string\n /** Color for article card titles and section headings. Example: `'#111827'` */\n headerColor?: string\n /** Color for body and excerpt text. Example: `'#6b7280'` */\n textColor?: string\n /** Background color for sections and cards. Example: `'#ffffff'` */\n backgroundColor?: string\n /** Color for \"Read More\" links and inline links. Example: `'#4f46e5'` */\n linkColor?: string\n /** Hover color for links. Example: `'#4338ca'` */\n linkHoverColor?: string\n /** Text decoration for links. Values: `'underline'` | `'none'` */\n linkTextDecoration?: string\n /** Font weight for headings. Example: `700` */\n headerFontWeight?: string | number\n /** Font size for article card titles. Example: `'1.25rem'` */\n headerFontSize?: string\n /** Font size for body text. Example: `'1rem'` */\n bodyFontSize?: string\n /** Line height for body text. Example: `'1.75'` */\n lineHeight?: string\n /** Border radius for cards. Example: `'0.5rem'` */\n borderRadius?: string\n}\n\n/** Per-category text used in meta descriptions and on the category hero page. */\nexport interface CategoryDescription {\n /** Short description — used in meta description and category grid card. */\n short: string\n /** Long description — rendered as a hero paragraph on the category page. */\n long?: string\n}\n\n/** Controls the comments feature. Attach to `ArticlesConfig.comments`. */\nexport interface CommentsConfig {\n /** Set to `true` to enable comments globally across all articles. */\n enabled: boolean\n /** Maximum reply nesting depth. Default: `1` (replies to top-level only). */\n maxDepth?: number\n /**\n * Per-article overrides keyed by slug.\n * Example: `{ 'sensitive-article': false }` disables comments on that article only.\n */\n perArticleOverride?: Record<string, boolean>\n}\n\n/** Text content for the hero section at the top of the articles listing page. */\nexport interface HeroConfig {\n /** Main heading displayed in the hero. Default: `'Vox Populus Insights'` */\n title?: string\n /** Subheading paragraph displayed beneath the title. Default: the built-in description. */\n description?: string\n}\n\n/** Top-level configuration object. Pass one instance to every library component. */\nexport interface ArticlesConfig {\n /** Canonical base URL of the site, used in metadata and JSON-LD. Example: `'https://yoursite.com'` */\n siteUrl: string\n /** Site name shown in metadata titles and JSON-LD publisher fields. Example: `'Vox Populus'` */\n siteName: string\n /** Number of articles shown per page in the grid and loaded on each \"Load more\" click. Default: `6` */\n pageSize?: number\n /** Number of category cards shown before a \"Load more categories\" button appears. Default: `8` */\n categoriesPageSize?: number\n /**\n * Ordered list of sections to render on the articles listing page.\n * Omit a key to hide that section entirely. Default: `['hero','search','featured','latest','categories']`\n */\n layout?: ArticlesSection[]\n /** CSS custom-property overrides for colors, fonts, and spacing. All fields optional. */\n theme?: ArticlesTheme\n /**\n * Descriptive text for each category, keyed by category slug.\n * A plain string is treated as the short description only.\n * Example: `{ 'campaigns': { short: 'Campaign tips', long: 'Full paragraph...' } }`\n */\n categoryDescriptions?: Record<string, string | CategoryDescription>\n /** Comments configuration. Omit or set `enabled: false` to hide comments entirely. */\n comments?: CommentsConfig\n /** Hero section title and description. Omit to use the built-in defaults. */\n hero?: HeroConfig\n}\n\nexport const DEFAULT_PAGE_SIZE = 6\nexport const DEFAULT_CATEGORIES_PAGE_SIZE = 8\n\nexport const DEFAULT_LAYOUT: ArticlesSection[] = [\n 'hero',\n 'search',\n 'featured',\n 'latest',\n 'categories',\n]\n","'use client'\n\nimport { useState, useCallback, useEffect, useRef } from 'react'\nimport { Article, CategoryInfo } from './articleTypes'\n\nexport interface UseArticlesReturn {\n articles: Article[]\n categories: CategoryInfo[]\n searchQuery: string\n loading: boolean\n error: string | null\n handleSearch: (query: string) => void\n}\n\nexport function useArticles(): UseArticlesReturn {\n const [searchQuery, setSearchQuery] = useState('')\n const debounceRef = useRef<ReturnType<typeof setTimeout> | null>(null)\n const [articles, setArticles] = useState<Article[]>([])\n const [categories, setCategories] = useState<CategoryInfo[]>([])\n const [loading, setLoading] = useState(true)\n const [error, setError] = useState<string | null>(null)\n\n const fetchArticles = useCallback(async (query: string) => {\n try {\n setLoading(true)\n const url = query ? `/api/articles?q=${encodeURIComponent(query)}` : '/api/articles'\n const response = await fetch(url)\n\n if (!response.ok) throw new Error('Failed to fetch articles')\n\n const data = await response.json()\n setArticles(data.articles)\n\n if (!query?.trim()) {\n const catMap = new Map<string, { count: number; featuredImage: string }>()\n for (const article of data.articles as Article[]) {\n for (const cat of article.categories) {\n if (!catMap.has(cat)) {\n catMap.set(cat, { count: 0, featuredImage: article.featuredImage })\n }\n catMap.get(cat)!.count++\n }\n }\n setCategories(\n Array.from(catMap.entries())\n .map(([name, { count, featuredImage }]) => ({\n name,\n slug: name\n .toLowerCase()\n .replaceAll(/\\s+/g, '-')\n .replaceAll(/[^a-z0-9-]/g, ''),\n count,\n featuredImage,\n }))\n .sort((a, b) => b.count - a.count)\n )\n }\n\n setError(null)\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to load articles')\n setArticles([])\n } finally {\n setLoading(false)\n }\n }, [])\n\n useEffect(() => {\n fetchArticles('')\n }, [fetchArticles])\n\n const handleSearch = useCallback(\n (query: string) => {\n setSearchQuery(query)\n if (debounceRef.current) clearTimeout(debounceRef.current)\n if (query === '') {\n fetchArticles('')\n } else if (query.length >= 3) {\n debounceRef.current = setTimeout(() => fetchArticles(query), 1000)\n }\n },\n [fetchArticles]\n )\n\n useEffect(\n () => () => {\n if (debounceRef.current) clearTimeout(debounceRef.current)\n },\n []\n )\n\n return { articles, categories, searchQuery, loading, error, handleSearch }\n}\n","'use client'\n\nimport React from 'react'\nimport type { Article, CategoryInfo } from './articleTypes'\nimport { ArticleCategoryGrid } from './ArticleCategoryGrid'\nimport { CollectionPageSchema } from './ArticleSchemas'\nimport { ArticleSearchBar } from './ArticleSearchBar'\nimport { ArticlesHero } from './ArticlesHero'\nimport { FeaturedArticle } from './FeaturedArticle'\nimport { LatestArticlesSection } from './LatestArticlesSection'\nimport {\n ArticlesConfig,\n ArticlesSection,\n ArticlesTheme,\n DEFAULT_CATEGORIES_PAGE_SIZE,\n DEFAULT_LAYOUT,\n DEFAULT_PAGE_SIZE,\n} from './articlesConfig'\nimport { useArticles } from './useArticles'\n\ninterface SectionContext {\n articles: Article[]\n displayedArticles: Article[]\n categories: CategoryInfo[]\n searchQuery: string\n loading: boolean\n error: string | null\n handleSearch: (query: string) => void\n pageSize: number\n categoriesPageSize: number\n}\n\nfunction buildThemeVars(theme?: ArticlesTheme): React.CSSProperties {\n if (!theme) return {}\n const vars: Record<string, string> = {}\n if (theme.fontFamily) vars['--articles-font-family'] = theme.fontFamily\n if (theme.headerColor) vars['--articles-header-color'] = theme.headerColor\n if (theme.textColor) vars['--articles-text-color'] = theme.textColor\n if (theme.backgroundColor) vars['--articles-bg-color'] = theme.backgroundColor\n if (theme.linkColor) vars['--articles-link-color'] = theme.linkColor\n if (theme.linkHoverColor) vars['--articles-link-hover-color'] = theme.linkHoverColor\n if (theme.linkTextDecoration) vars['--articles-link-decoration'] = theme.linkTextDecoration\n if (theme.headerFontWeight) vars['--articles-header-font-weight'] = String(theme.headerFontWeight)\n if (theme.headerFontSize) vars['--articles-header-font-size'] = theme.headerFontSize\n if (theme.bodyFontSize) vars['--articles-body-font-size'] = theme.bodyFontSize\n if (theme.lineHeight) vars['--articles-line-height'] = theme.lineHeight\n if (theme.borderRadius) vars['--articles-border-radius'] = theme.borderRadius\n return vars as React.CSSProperties\n}\n\nfunction renderSection(\n section: ArticlesSection,\n ctx: SectionContext,\n config: ArticlesConfig\n): React.ReactNode {\n const {\n articles,\n displayedArticles,\n categories,\n searchQuery,\n loading,\n error,\n handleSearch,\n pageSize,\n categoriesPageSize,\n } = ctx\n\n switch (section) {\n case 'hero':\n return (\n <ArticlesHero\n key=\"hero\"\n title={config.hero?.title}\n description={config.hero?.description}\n />\n )\n\n case 'search':\n return (\n <ArticleSearchBar\n key=\"search\"\n value={searchQuery}\n onChange={handleSearch}\n disabled={loading}\n resultCount={articles.length}\n />\n )\n\n case 'featured':\n return articles.length > 0 && !searchQuery ? (\n <FeaturedArticle key=\"featured\" article={articles[0]} />\n ) : null\n\n case 'latest':\n if (loading && articles.length === 0) {\n return (\n <section key=\"latest\" id=\"latest\" className=\"py-16 bg-muted/50 flex-1\">\n <div className=\"max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 flex items-center justify-center py-24\">\n <div className=\"text-center\">\n <div className=\"animate-spin rounded-full h-12 w-12 border-b-2 border-primary mx-auto mb-4\" />\n <p className=\"text-muted-foreground\">Loading articles...</p>\n </div>\n </div>\n </section>\n )\n }\n if (error) {\n return (\n <section key=\"latest\" id=\"latest\" className=\"py-16 bg-muted/50 flex-1\">\n <div className=\"max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 flex items-center justify-center\">\n <div className=\"bg-red-50 border border-red-200 rounded-lg p-6 max-w-md text-center\">\n <h3 className=\"text-lg font-semibold text-red-800 mb-2\">Error Loading Articles</h3>\n <p className=\"text-red-600 mb-4\">{error}</p>\n <button\n type=\"button\"\n onClick={() => handleSearch('')}\n className=\"inline-flex items-center justify-center px-4 py-2 border border-input bg-background hover:bg-accent rounded-md font-medium text-sm transition-colors\"\n >\n Try again\n </button>\n </div>\n </div>\n </section>\n )\n }\n return (\n <LatestArticlesSection\n key=\"latest\"\n articles={displayedArticles}\n searchQuery={searchQuery}\n onClearSearch={() => handleSearch('')}\n pageSize={pageSize}\n />\n )\n\n case 'categories':\n return searchQuery ? null : (\n <ArticleCategoryGrid\n key=\"categories\"\n categories={categories}\n pageSize={categoriesPageSize}\n />\n )\n\n // 'newsletter' is intentionally omitted — inject it via the layout prop in the consuming app\n default:\n return null\n }\n}\n\nexport function ArticlesPage({ config }: Readonly<{ config: ArticlesConfig }>) {\n const state = useArticles()\n const layout = config.layout ?? DEFAULT_LAYOUT\n const pageSize = config.pageSize ?? DEFAULT_PAGE_SIZE\n const categoriesPageSize = config.categoriesPageSize ?? DEFAULT_CATEGORIES_PAGE_SIZE\n const themeVars = buildThemeVars(config.theme)\n\n const hasFeatured = layout.includes('featured')\n const articlesWithoutFeatured = hasFeatured ? state.articles.slice(1) : state.articles\n const displayedArticles = state.searchQuery ? state.articles : articlesWithoutFeatured\n\n const ctx: SectionContext = { ...state, displayedArticles, pageSize, categoriesPageSize }\n\n return (\n <div style={themeVars}>\n {layout.map((section) => renderSection(section, ctx, config))}\n {state.articles.length > 0 && (\n <CollectionPageSchema\n title={`Articles | ${config.siteName}`}\n description={`Browse all ${state.articles.length} articles on ${config.siteName}.`}\n url={`${config.siteUrl}/articles`}\n articleCount={state.articles.length}\n />\n )}\n </div>\n )\n}\n","'use client'\n\nimport Image from 'next/image'\nimport Link from 'next/link'\nimport { BreadcrumbSchema } from './ArticleSchemas'\nimport { LatestArticles } from './LatestArticles'\nimport { ArticlesConfig, DEFAULT_PAGE_SIZE } from './articlesConfig'\nimport type { Article } from './articleTypes'\n\nfunction getCategoryDescription(\n config: ArticlesConfig,\n slug: string,\n categoryName: string\n): { short: string; long?: string } {\n const raw = config.categoryDescriptions?.[slug]\n if (!raw) return { short: `Browse all articles in ${categoryName}.` }\n if (typeof raw === 'string') return { short: raw }\n return raw\n}\n\ntype CategoryArticlesPageProps = Readonly<{\n category: string\n articles: Article[]\n config: ArticlesConfig\n}>\n\nexport function CategoryArticlesPage({ category, articles, config }: CategoryArticlesPageProps) {\n if (articles.length === 0) return null\n\n const categoryName = articles[0].category\n const heroImage = articles[0].featuredImage\n const description = getCategoryDescription(config, category, categoryName)\n const pageSize = config.pageSize ?? DEFAULT_PAGE_SIZE\n const siteUrl = config.siteUrl.replace(/\\/$/, '')\n\n return (\n <div>\n <BreadcrumbSchema\n items={[\n { name: 'Home', url: siteUrl },\n { name: 'Articles', url: `${siteUrl}/articles` },\n { name: categoryName, url: `${siteUrl}/articles/category/${category}` },\n ]}\n />\n {/* Hero */}\n <section className=\"relative h-72 md:h-96 flex items-center justify-center overflow-hidden\">\n <Image\n src={heroImage}\n alt={categoryName}\n fill\n sizes=\"100vw\"\n className=\"object-cover\"\n priority\n />\n <div className=\"absolute inset-0 bg-black/60\" />\n <div className=\"relative z-10 text-center text-white px-4\">\n <p className=\"text-sm font-semibold uppercase tracking-widest mb-3 opacity-80\">\n Category\n </p>\n <h1 className=\"text-4xl md:text-5xl font-bold mb-3\">{categoryName}</h1>\n {description.long && (\n <p className=\"text-lg opacity-90 max-w-2xl mx-auto mt-2\">{description.long}</p>\n )}\n <p className=\"text-lg opacity-70 mt-2\">\n {articles.length} article{articles.length === 1 ? '' : 's'}\n </p>\n </div>\n </section>\n\n {/* Articles */}\n <section className=\"py-16 bg-muted/50 flex-1\">\n <div className=\"max-w-7xl mx-auto px-4 sm:px-6 lg:px-8\">\n <div className=\"mb-8 flex items-center justify-between\">\n <Link href=\"/articles\" className=\"text-sm text-primary hover:underline\">\n ← All Articles\n </Link>\n <p className=\"text-sm text-muted-foreground\">{description.short}</p>\n </div>\n <LatestArticles articles={articles} pageSize={pageSize} />\n </div>\n </section>\n </div>\n )\n}\n","'use client'\n\nimport { useSession, signIn } from 'next-auth/react'\nimport { useCallback, useEffect, useState } from 'react'\nimport { CommentForm } from './CommentForm'\nimport { CommentThread } from './CommentThread'\nimport type { CommentsConfig } from './articlesConfig'\nimport type { ArticleCommentWithReplies } from './commentTypes'\n\ninterface CommentsSectionProps {\n readonly articleSlug: string\n readonly config: CommentsConfig\n}\n\nexport function CommentsSection({ articleSlug, config }: CommentsSectionProps) {\n const { data: session, status } = useSession()\n const [comments, setComments] = useState<ArticleCommentWithReplies[]>([])\n const [loading, setLoading] = useState(true)\n const [fetchError, setFetchError] = useState<string | null>(null)\n\n const perArticleEnabled = config.perArticleOverride?.[articleSlug] ?? true\n const enabled = config.enabled && perArticleEnabled\n\n const currentUserId = session?.user?.email ?? session?.user?.name ?? undefined\n\n const fetchComments = useCallback(async () => {\n setLoading(true)\n setFetchError(null)\n try {\n const res = await fetch(`/api/articles/${articleSlug}/comments`)\n if (!res.ok) throw new Error('Failed to load comments')\n const data = (await res.json()) as { comments: ArticleCommentWithReplies[] }\n setComments(data.comments)\n } catch {\n setFetchError('Could not load comments. Please try again.')\n } finally {\n setLoading(false)\n }\n }, [articleSlug])\n\n useEffect(() => {\n if (enabled) {\n fetchComments().catch(() => undefined)\n }\n }, [fetchComments, enabled])\n\n if (!enabled) return null\n\n const count = comments.length\n const label = count === 1 ? '1 comment' : `${count} comments`\n\n return (\n <section aria-label=\"Comments\" className=\"mt-12 pt-8 border-t border-border space-y-6\">\n <h2 className=\"text-xl font-semibold text-foreground\">{loading ? 'Comments' : label}</h2>\n\n {status === 'authenticated' ? (\n <CommentForm articleSlug={articleSlug} onSuccess={fetchComments} />\n ) : (\n <p className=\"text-sm text-muted-foreground\">\n <button\n onClick={() => signIn()}\n className=\"text-primary underline hover:no-underline focus:outline-none\"\n >\n Sign in\n </button>{' '}\n to join the discussion.\n </p>\n )}\n\n {fetchError && <p className=\"text-sm text-destructive\">{fetchError}</p>}\n\n {loading ? (\n <p className=\"text-sm text-muted-foreground\">Loading comments…</p>\n ) : (\n <CommentThread\n comments={comments}\n articleSlug={articleSlug}\n currentUserId={currentUserId}\n onRefresh={fetchComments}\n />\n )}\n </section>\n )\n}\n","'use client'\n\nimport { useState } from 'react'\n\nconst MAX_LENGTH = 2000\nconst WARN_THRESHOLD = 1800\n\nfunction submitLabel(submitting: boolean, parentId?: string): string {\n if (submitting) return 'Posting…'\n return parentId ? 'Reply' : 'Post comment'\n}\n\ninterface CommentFormProps {\n readonly articleSlug: string\n readonly parentId?: string\n readonly onSuccess: () => void\n readonly onCancel?: () => void\n}\n\nexport function CommentForm({ articleSlug, parentId, onSuccess, onCancel }: CommentFormProps) {\n const [body, setBody] = useState('')\n const [submitting, setSubmitting] = useState(false)\n const [error, setError] = useState<string | null>(null)\n\n const remaining = MAX_LENGTH - body.length\n\n async function handleSubmit(e: React.FormEvent) {\n e.preventDefault()\n if (!body.trim() || submitting) return\n\n setSubmitting(true)\n setError(null)\n\n try {\n const res = await fetch(`/api/articles/${articleSlug}/comments`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ body: body.trim(), parentId: parentId ?? null }),\n })\n\n if (!res.ok) {\n const data = (await res.json()) as { error?: string }\n setError(data.error ?? 'Failed to post comment')\n return\n }\n\n setBody('')\n onSuccess()\n } catch {\n setError('Network error — please try again')\n } finally {\n setSubmitting(false)\n }\n }\n\n return (\n <form onSubmit={handleSubmit} className=\"space-y-2\">\n <textarea\n value={body}\n onChange={(e) => setBody(e.target.value)}\n maxLength={MAX_LENGTH}\n rows={parentId ? 3 : 4}\n placeholder={parentId ? 'Write a reply…' : 'Join the discussion…'}\n disabled={submitting}\n className=\"w-full rounded-md border border-border bg-background px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-primary/50 disabled:opacity-50 resize-none\"\n aria-label={parentId ? 'Reply text' : 'Comment text'}\n />\n\n {remaining <= MAX_LENGTH - WARN_THRESHOLD && (\n <p className={`text-xs ${remaining < 0 ? 'text-destructive' : 'text-muted-foreground'}`}>\n {remaining} characters remaining\n </p>\n )}\n\n {error && <p className=\"text-xs text-destructive\">{error}</p>}\n\n <div className=\"flex gap-2\">\n <button\n type=\"submit\"\n disabled={submitting || body.trim().length === 0 || body.length > MAX_LENGTH}\n className=\"rounded-md bg-primary px-4 py-1.5 text-sm font-medium text-primary-foreground hover:bg-primary/90 disabled:opacity-50 disabled:cursor-not-allowed\"\n >\n {submitLabel(submitting, parentId)}\n </button>\n\n {onCancel && (\n <button\n type=\"button\"\n onClick={onCancel}\n className=\"rounded-md px-4 py-1.5 text-sm font-medium text-muted-foreground hover:text-foreground\"\n >\n Cancel\n </button>\n )}\n </div>\n </form>\n )\n}\n","'use client'\n\nimport { useState } from 'react'\nimport { CommentForm } from './CommentForm'\nimport type { ArticleComment, ArticleCommentWithReplies } from './commentTypes'\n\nfunction relativeTime(iso: string): string {\n const diff = Date.now() - new Date(iso).getTime()\n const minutes = Math.floor(diff / 60_000)\n if (minutes < 1) return 'just now'\n if (minutes < 60) return `${minutes}m ago`\n const hours = Math.floor(minutes / 60)\n if (hours < 24) return `${hours}h ago`\n const days = Math.floor(hours / 24)\n if (days < 30) return `${days}d ago`\n const months = Math.floor(days / 30)\n if (months < 12) return `${months}mo ago`\n return `${Math.floor(months / 12)}y ago`\n}\n\ninterface CommentItemProps {\n readonly comment: ArticleCommentWithReplies | ArticleComment\n readonly articleSlug: string\n readonly currentUserId?: string\n readonly isReply?: boolean\n readonly onRefresh: () => void\n}\n\nexport function CommentItem({\n comment,\n articleSlug,\n currentUserId,\n isReply = false,\n onRefresh,\n}: CommentItemProps) {\n const [showReplyForm, setShowReplyForm] = useState(false)\n const [deleting, setDeleting] = useState(false)\n\n const replies = 'replies' in comment ? comment.replies : []\n const canReply = !isReply && !!currentUserId && !comment.isDeleted\n const canDelete = !!currentUserId && currentUserId === comment.authorId && !comment.isDeleted\n\n async function handleDelete() {\n if (deleting) return\n setDeleting(true)\n try {\n await fetch(`/api/articles/${articleSlug}/comments/${comment.id}`, { method: 'DELETE' })\n onRefresh()\n } finally {\n setDeleting(false)\n }\n }\n\n return (\n <div className={`${isReply ? 'ml-8 border-l-2 border-border pl-4' : ''}`}>\n <div className=\"rounded-lg bg-muted/40 p-3 space-y-1\">\n {comment.isDeleted ? (\n <p className=\"text-sm italic text-muted-foreground\">Comment removed</p>\n ) : (\n <>\n <div className=\"flex items-center justify-between gap-2\">\n <span className=\"text-sm font-medium text-foreground\">{comment.authorName}</span>\n <span className=\"text-xs text-muted-foreground\">\n {relativeTime(comment.createdAt)}\n </span>\n </div>\n <p className=\"text-sm text-foreground whitespace-pre-wrap break-words\">\n {comment.body}\n </p>\n <div className=\"flex gap-3 pt-1\">\n {canReply && (\n <button\n onClick={() => setShowReplyForm((v) => !v)}\n className=\"text-xs text-muted-foreground hover:text-foreground\"\n >\n {showReplyForm ? 'Cancel' : 'Reply'}\n </button>\n )}\n {canDelete && (\n <button\n onClick={handleDelete}\n disabled={deleting}\n className=\"text-xs text-muted-foreground hover:text-destructive disabled:opacity-50\"\n >\n {deleting ? 'Deleting…' : 'Delete'}\n </button>\n )}\n </div>\n </>\n )}\n </div>\n\n {showReplyForm && (\n <div className=\"mt-2 ml-2\">\n <CommentForm\n articleSlug={articleSlug}\n parentId={comment.id}\n onSuccess={() => {\n setShowReplyForm(false)\n onRefresh()\n }}\n onCancel={() => setShowReplyForm(false)}\n />\n </div>\n )}\n\n {replies.length > 0 && (\n <div className=\"mt-2 space-y-2\">\n {replies.map((reply) => (\n <CommentItem\n key={reply.id}\n comment={{ ...reply, replies: [] }}\n articleSlug={articleSlug}\n currentUserId={currentUserId}\n isReply\n onRefresh={onRefresh}\n />\n ))}\n </div>\n )}\n </div>\n )\n}\n","'use client'\n\nimport { CommentItem } from './CommentItem'\nimport type { ArticleCommentWithReplies } from './commentTypes'\n\ninterface CommentThreadProps {\n readonly comments: ArticleCommentWithReplies[]\n readonly articleSlug: string\n readonly currentUserId?: string\n readonly onRefresh: () => void\n}\n\nexport function CommentThread({\n comments,\n articleSlug,\n currentUserId,\n onRefresh,\n}: CommentThreadProps) {\n if (comments.length === 0) {\n return (\n <p className=\"text-sm text-muted-foreground text-center py-6\">\n No comments yet. Be the first to share your thoughts.\n </p>\n )\n }\n\n return (\n <div className=\"space-y-4\">\n {comments.map((comment) => (\n <CommentItem\n key={comment.id}\n comment={comment}\n articleSlug={articleSlug}\n currentUserId={currentUserId}\n onRefresh={onRefresh}\n />\n ))}\n </div>\n )\n}\n"],"mappings":"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","names":["jsx","jsx","jsxs","Link","jsx","jsxs","Link","Image","jsx","jsxs","Search","useState","useEffect","Image","Link","ArrowRight","Calendar","Clock","jsx","jsxs","jsx","jsxs","useState","useEffect","jsx","jsxs","Search","useState","useEffect","useState","useEffect","jsx","jsxs","Image","Link","jsx","jsxs","Image","Link","useCallback","useEffect","useState","useState","jsx","jsxs","useState","e","useState","jsx","jsxs","useState","jsx","jsx","jsxs","useState","useCallback","useEffect"]}
package/dist/server.cjs CHANGED
@@ -243,8 +243,7 @@ var customRenderer = () => {
243
243
  const styleLinks = (nodes) => {
244
244
  nodes.forEach((n) => {
245
245
  var _a2, _b2;
246
- if (n.type !== "element")
247
- return;
246
+ if (n.type !== "element") return;
248
247
  const el = n;
249
248
  if (el.tagName === "a") {
250
249
  const isBackRef = ((_a2 = el.properties) == null ? void 0 : _a2["dataFootnoteBackref"]) !== void 0 || ((_b2 = el.children[0]) == null ? void 0 : _b2.type) === "text" && el.children[0].value === "\u21A9";
@@ -260,8 +259,7 @@ var customRenderer = () => {
260
259
  el.properties.className = "text-primary hover:underline break-all";
261
260
  }
262
261
  }
263
- if (el.children)
264
- styleLinks(el.children);
262
+ if (el.children) styleLinks(el.children);
265
263
  });
266
264
  };
267
265
  styleLinks(liEl.children);
@@ -338,10 +336,8 @@ function findArticleImage(slug) {
338
336
  const articleDir = import_node_path.default.join(articlesDirectory, slug);
339
337
  const entries = import_node_fs.default.readdirSync(articleDir, { withFileTypes: true });
340
338
  const names = entries.map((entry) => {
341
- if (typeof entry === "string")
342
- return entry;
343
- if (entry && typeof entry.name === "string")
344
- return entry.name;
339
+ if (typeof entry === "string") return entry;
340
+ if (entry && typeof entry.name === "string") return entry.name;
345
341
  return null;
346
342
  }).filter((name) => Boolean(name));
347
343
  const imageExtensions = [".png", ".jpg", ".jpeg", ".gif", ".webp"];
@@ -354,11 +350,9 @@ function findArticleImage(slug) {
354
350
  }
355
351
  }
356
352
  function sanitizeImagePath2(rawPath, articleSlug) {
357
- if (!rawPath || typeof rawPath !== "string")
358
- return null;
353
+ if (!rawPath || typeof rawPath !== "string") return null;
359
354
  const cleanPath = rawPath.replaceAll(/[\x00-\x1f\x7f-\x9f]/g, "");
360
- if (cleanPath.startsWith("http://") || cleanPath.startsWith("https://"))
361
- return cleanPath;
355
+ if (cleanPath.startsWith("http://") || cleanPath.startsWith("https://")) return cleanPath;
362
356
  if (cleanPath.includes("..") || cleanPath.includes("\\") || cleanPath.startsWith("/")) {
363
357
  console.warn(`Potentially unsafe image path detected: ${cleanPath}`);
364
358
  return null;
@@ -379,8 +373,7 @@ function sanitizeImagePath2(rawPath, articleSlug) {
379
373
  }
380
374
  function getAvailableArticleSlugs() {
381
375
  try {
382
- if (!import_node_fs.default.existsSync(articlesDirectory))
383
- return [];
376
+ if (!import_node_fs.default.existsSync(articlesDirectory)) return [];
384
377
  const items = import_node_fs.default.readdirSync(articlesDirectory, { withFileTypes: true });
385
378
  return items.filter((item) => item.isDirectory()).filter((dir) => {
386
379
  const articleDir = import_node_path.default.join(articlesDirectory, dir.name);
@@ -390,12 +383,9 @@ function getAvailableArticleSlugs() {
390
383
  });
391
384
  return entries.some((entry) => {
392
385
  const name = typeof entry === "string" ? entry : entry == null ? void 0 : entry.name;
393
- if (name !== "article.md")
394
- return false;
395
- if (typeof entry === "string")
396
- return true;
397
- if (typeof (entry == null ? void 0 : entry.isFile) === "function")
398
- return entry.isFile();
386
+ if (name !== "article.md") return false;
387
+ if (typeof entry === "string") return true;
388
+ if (typeof (entry == null ? void 0 : entry.isFile) === "function") return entry.isFile();
399
389
  return true;
400
390
  });
401
391
  } catch (e) {
@@ -411,8 +401,7 @@ function getArticleSummary(slug) {
411
401
  return __async(this, null, function* () {
412
402
  try {
413
403
  const articlePath = import_node_path.default.join(articlesDirectory, slug, "article.md");
414
- if (!import_node_fs.default.existsSync(articlePath))
415
- return null;
404
+ if (!import_node_fs.default.existsSync(articlePath)) return null;
416
405
  const fileContent = import_node_fs.default.readFileSync(articlePath, "utf8");
417
406
  const { data, content: markdownContent } = (0, import_gray_matter.default)(fileContent);
418
407
  const readTime = getReadingTime(markdownContent);
@@ -457,8 +446,7 @@ function getArticleSummary(slug) {
457
446
  var getArticleMetadata = (0, import_react.cache)((slug) => __async(void 0, null, function* () {
458
447
  try {
459
448
  const articlePath = import_node_path.default.join(articlesDirectory, slug, "article.md");
460
- if (!import_node_fs.default.existsSync(articlePath))
461
- return null;
449
+ if (!import_node_fs.default.existsSync(articlePath)) return null;
462
450
  const fileContent = import_node_fs.default.readFileSync(articlePath, "utf8");
463
451
  const { data, content: markdownContent } = (0, import_gray_matter.default)(fileContent);
464
452
  const htmlContent = yield markdownToHtml(markdownContent, slug);
@@ -507,12 +495,9 @@ var getAllArticles = (0, import_react.cache)(() => __async(void 0, null, functio
507
495
  const articles = yield Promise.all(slugs.map((slug) => getArticleSummary(slug)));
508
496
  const currentDate = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
509
497
  return articles.filter((article) => article !== null).filter((article) => !article.date || article.date <= currentDate).sort((a, b) => {
510
- if (!a.date && !b.date)
511
- return 0;
512
- if (!a.date)
513
- return 1;
514
- if (!b.date)
515
- return -1;
498
+ if (!a.date && !b.date) return 0;
499
+ if (!a.date) return 1;
500
+ if (!b.date) return -1;
516
501
  return new Date(b.date).getTime() - new Date(a.date).getTime();
517
502
  });
518
503
  }));
@@ -520,8 +505,7 @@ function getAdjacentArticles(currentSlug) {
520
505
  return __async(this, null, function* () {
521
506
  const allArticles = yield getAllArticles();
522
507
  const currentIndex = allArticles.findIndex((article) => article.slug === currentSlug);
523
- if (currentIndex === -1)
524
- return { previous: null, next: null };
508
+ if (currentIndex === -1) return { previous: null, next: null };
525
509
  const previous = currentIndex < allArticles.length - 1 ? allArticles[currentIndex + 1] : null;
526
510
  const next = currentIndex > 0 ? allArticles[currentIndex - 1] : null;
527
511
  return { previous, next };
@@ -529,8 +513,7 @@ function getAdjacentArticles(currentSlug) {
529
513
  }
530
514
  function searchArticles(query) {
531
515
  return __async(this, null, function* () {
532
- if (!(query == null ? void 0 : query.trim()))
533
- return getAllArticles();
516
+ if (!(query == null ? void 0 : query.trim())) return getAllArticles();
534
517
  const articles = yield getAllArticles();
535
518
  const searchTerm = query.toLowerCase().trim();
536
519
  return articles.filter((article) => {
@@ -665,8 +648,7 @@ function generateCategoryMetadata(categorySlug, config) {
665
648
  return __async(this, null, function* () {
666
649
  var _a, _b;
667
650
  const articles = yield getArticlesByCategory(categorySlug);
668
- if (articles.length === 0)
669
- return { title: "Category Not Found" };
651
+ if (articles.length === 0) return { title: "Category Not Found" };
670
652
  const categoryName = articles[0].category;
671
653
  const siteUrl = config.siteUrl.replace(/\/$/, "");
672
654
  const categoryUrl = `${siteUrl}/articles/category/${categorySlug}`;