@fullstackdatasolutions/articles 0.2.0 → 0.3.0

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/README.md CHANGED
@@ -2,6 +2,8 @@
2
2
 
3
3
  A self-contained Next.js articles library providing components, server utilities, SEO helpers, and a comments system. Drop it into any Next.js + Tailwind CSS v4 app.
4
4
 
5
+ Built by [Full Stack Data Solutions](https://fullstackdatasolutions.com).
6
+
5
7
  ---
6
8
 
7
9
  ## Quick start
@@ -169,6 +171,29 @@ hero: {
169
171
 
170
172
  ---
171
173
 
174
+ ## ArticleDetailHero
175
+
176
+ `ArticleDetailHero` renders the article's featured image full-width behind the title, category tags, and metadata (date, read time, author). Use it in your `app/articles/[slug]/page.tsx` instead of building a custom hero.
177
+
178
+ ```tsx
179
+ import { ArticleDetailHero } from '@fullstackdatasolutions/articles'
180
+
181
+ <ArticleDetailHero
182
+ article={article}
183
+ categoryBasePath="/articles/category"
184
+ />
185
+ ```
186
+
187
+ | Prop | Type | Required | Description |
188
+ |---|---|---|---|
189
+ | `article` | `Article` | yes | Article object returned by `getArticleMetadata` |
190
+ | `categoryBasePath` | `string` | no | Base path for category links (e.g. `"/articles/category"`). Omit to render categories as plain text. |
191
+
192
+ - Category tags are capped at 4. When an article has more than 4 categories, only the first 4 are shown.
193
+ - All layout styles are inline so the component renders correctly regardless of whether `@source` is configured for the package.
194
+
195
+ ---
196
+
172
197
  ## Comments setup
173
198
 
174
199
  ### 1. Enable in config
@@ -232,7 +257,7 @@ JSON-LD structured data (`ArticleSchema`, `BreadcrumbSchema`, `CollectionPageSch
232
257
  | `title` | `string` | yes | Article title |
233
258
  | `excerpt` | `string` | yes | One-sentence summary for cards and meta |
234
259
  | `author` | `string` | no | Defaults to `'Andrew Blase'` |
235
- | `tags` | `string[]` | no | Used as categories. First tag = primary category. |
260
+ | `tags` | `string[]` | no | Used as categories. First tag = primary category. ArticleDetailHero displays at most 4 category tags; articles with more than 4 show only the first 4 in the hero. |
236
261
  | `date` | `YYYY-MM-DD` | no | Omit to publish immediately. Future dates hide until that date. |
237
262
  | `featuredImage` | `string` | no | Filename relative to the article directory. Auto-detected if omitted. |
238
263
 
package/dist/index.cjs CHANGED
@@ -71,6 +71,7 @@ var src_exports = {};
71
71
  __export(src_exports, {
72
72
  ArticleCard: () => ArticleCard,
73
73
  ArticleCategoryGrid: () => ArticleCategoryGrid,
74
+ ArticleDetailHero: () => ArticleDetailHero,
74
75
  ArticleSchema: () => ArticleSchema,
75
76
  ArticleSearchBar: () => ArticleSearchBar,
76
77
  ArticlesHero: () => ArticlesHero,
@@ -110,37 +111,26 @@ function ArticleCategoryGrid({ categories, pageSize = 8 }) {
110
111
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h2", { className: "text-3xl font-bold text-foreground mb-4", children: "Browse by Category" }),
111
112
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "text-lg text-muted-foreground", children: "Explore our articles by topic to find the insights most relevant to your interests." })
112
113
  ] }),
113
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
114
- "div",
115
- {
116
- className: "sm:grid-cols-4",
117
- style: {
118
- display: "grid",
119
- gridTemplateColumns: "repeat(1, minmax(0, 1fr))",
120
- gap: "1.5rem"
121
- },
122
- children: visible.map((cat) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_link.default, { href: `/articles/category/${cat.slug}`, children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "group overflow-hidden rounded-xl border border-border shadow-sm hover:shadow-lg transition-all duration-300 cursor-pointer bg-card", children: [
123
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "overflow-hidden", style: { position: "relative", height: "13rem" }, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
124
- import_image.default,
125
- {
126
- src: cat.featuredImage,
127
- alt: cat.name,
128
- fill: true,
129
- sizes: "(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw",
130
- className: "object-cover group-hover:scale-105 transition-transform duration-300"
131
- }
132
- ) }),
133
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "p-4 text-center", children: [
134
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h3", { className: "font-semibold text-foreground text-base", children: cat.name }),
135
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("p", { className: "text-sm text-muted-foreground mt-1", children: [
136
- cat.count,
137
- " ",
138
- cat.count === 1 ? "article" : "articles"
139
- ] })
140
- ] })
141
- ] }) }, cat.slug))
142
- }
143
- ),
114
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "grid md:grid-cols-2 lg:grid-cols-3 gap-6", children: visible.map((cat) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_link.default, { href: `/articles/category/${cat.slug}`, children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "group overflow-hidden rounded-xl border border-border shadow-sm hover:shadow-lg transition-all duration-300 cursor-pointer bg-card", children: [
115
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "overflow-hidden", style: { position: "relative", height: "13rem" }, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
116
+ import_image.default,
117
+ {
118
+ src: cat.featuredImage,
119
+ alt: cat.name,
120
+ fill: true,
121
+ sizes: "(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw",
122
+ className: "object-cover group-hover:scale-105 transition-transform duration-300"
123
+ }
124
+ ) }),
125
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "p-4 text-center", children: [
126
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h3", { className: "font-semibold text-foreground text-base", children: cat.name }),
127
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("p", { className: "text-sm text-muted-foreground mt-1", children: [
128
+ cat.count,
129
+ " ",
130
+ cat.count === 1 ? "article" : "articles"
131
+ ] })
132
+ ] })
133
+ ] }) }, cat.slug)) }),
144
134
  hasMore && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "mt-10 text-center", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
145
135
  "button",
146
136
  {
@@ -670,10 +660,110 @@ function ArticlesPage({ config }) {
670
660
  ] });
671
661
  }
672
662
 
673
- // src/CategoryArticlesPage.tsx
663
+ // src/ArticleDetailHero.tsx
674
664
  var import_image4 = __toESM(require("next/image"), 1);
675
665
  var import_link5 = __toESM(require("next/link"), 1);
666
+ var import_lucide_react5 = require("lucide-react");
676
667
  var import_jsx_runtime10 = require("react/jsx-runtime");
668
+ function toSlug(cat) {
669
+ return cat.toLowerCase().replaceAll(/\s+/g, "-").replaceAll(/[^a-z0-9-]/g, "");
670
+ }
671
+ function ArticleDetailHero({ article, categoryBasePath }) {
672
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
673
+ "section",
674
+ {
675
+ style: {
676
+ position: "relative",
677
+ height: "18rem",
678
+ display: "flex",
679
+ alignItems: "center",
680
+ justifyContent: "center",
681
+ overflow: "hidden"
682
+ },
683
+ children: [
684
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
685
+ import_image4.default,
686
+ {
687
+ src: article.featuredImage,
688
+ alt: article.title,
689
+ fill: true,
690
+ sizes: "100vw",
691
+ className: "object-cover",
692
+ priority: true
693
+ }
694
+ ),
695
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
696
+ "div",
697
+ {
698
+ style: {
699
+ position: "absolute",
700
+ inset: 0,
701
+ backgroundColor: "rgba(0,0,0,0.6)"
702
+ }
703
+ }
704
+ ),
705
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
706
+ "div",
707
+ {
708
+ style: {
709
+ position: "relative",
710
+ zIndex: 10,
711
+ textAlign: "center",
712
+ color: "white",
713
+ padding: "0 1rem",
714
+ maxWidth: "56rem",
715
+ margin: "0 auto",
716
+ width: "100%"
717
+ },
718
+ children: [
719
+ article.categories && article.categories.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { style: { display: "flex", flexWrap: "wrap", justifyContent: "center", gap: "0.5rem", marginBottom: "1rem" }, children: article.categories.slice(0, 4).map(
720
+ (cat) => categoryBasePath ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
721
+ import_link5.default,
722
+ {
723
+ href: `${categoryBasePath}/${toSlug(cat)}`,
724
+ className: "inline-block bg-white/20 text-white text-sm font-medium px-3 py-1 rounded-full hover:bg-white/30 transition-colors",
725
+ children: cat
726
+ },
727
+ cat
728
+ ) : /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
729
+ "span",
730
+ {
731
+ className: "inline-block bg-white/20 text-white text-sm font-medium px-3 py-1 rounded-full",
732
+ children: cat
733
+ },
734
+ cat
735
+ )
736
+ ) }),
737
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("h1", { className: "text-4xl md:text-5xl font-bold mb-4", children: article.title }),
738
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { style: { display: "flex", alignItems: "center", justifyContent: "center", gap: "1.5rem", fontSize: "0.875rem", color: "rgba(255,255,255,0.8)" }, children: [
739
+ article.date && /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: "0.5rem" }, children: [
740
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_lucide_react5.Calendar, { className: "h-4 w-4" }),
741
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { children: article.date })
742
+ ] }),
743
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: "0.5rem" }, children: [
744
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_lucide_react5.Clock, { className: "h-4 w-4" }),
745
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { children: article.readTime })
746
+ ] }),
747
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: "0.5rem" }, children: [
748
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_lucide_react5.User, { className: "h-4 w-4" }),
749
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("span", { children: [
750
+ "By ",
751
+ article.author
752
+ ] })
753
+ ] })
754
+ ] })
755
+ ]
756
+ }
757
+ )
758
+ ]
759
+ }
760
+ );
761
+ }
762
+
763
+ // src/CategoryArticlesPage.tsx
764
+ var import_image5 = __toESM(require("next/image"), 1);
765
+ var import_link6 = __toESM(require("next/link"), 1);
766
+ var import_jsx_runtime11 = require("react/jsx-runtime");
677
767
  function getCategoryDescription(config, slug, categoryName) {
678
768
  var _a;
679
769
  const raw = (_a = config.categoryDescriptions) == null ? void 0 : _a[slug];
@@ -689,8 +779,8 @@ function CategoryArticlesPage({ category, articles, config }) {
689
779
  const description = getCategoryDescription(config, category, categoryName);
690
780
  const pageSize = (_a = config.pageSize) != null ? _a : DEFAULT_PAGE_SIZE;
691
781
  const siteUrl = config.siteUrl.replace(/\/$/, "");
692
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { children: [
693
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
782
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { children: [
783
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
694
784
  BreadcrumbSchema,
695
785
  {
696
786
  items: [
@@ -700,9 +790,9 @@ function CategoryArticlesPage({ category, articles, config }) {
700
790
  ]
701
791
  }
702
792
  ),
703
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("section", { className: "relative h-72 md:h-96 flex items-center justify-center overflow-hidden", children: [
704
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
705
- import_image4.default,
793
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("section", { className: "relative h-72 md:h-96 flex items-center justify-center overflow-hidden", children: [
794
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
795
+ import_image5.default,
706
796
  {
707
797
  src: heroImage,
708
798
  alt: categoryName,
@@ -712,24 +802,24 @@ function CategoryArticlesPage({ category, articles, config }) {
712
802
  priority: true
713
803
  }
714
804
  ),
715
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "absolute inset-0 bg-black/60" }),
716
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "relative z-10 text-center text-white px-4", children: [
717
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("p", { className: "text-sm font-semibold uppercase tracking-widest mb-3 opacity-80", children: "Category" }),
718
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("h1", { className: "text-4xl md:text-5xl font-bold mb-3", children: categoryName }),
719
- description.long && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("p", { className: "text-lg opacity-90 max-w-2xl mx-auto mt-2", children: description.long }),
720
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("p", { className: "text-lg opacity-70 mt-2", children: [
805
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { className: "absolute inset-0 bg-black/60" }),
806
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "relative z-10 text-center text-white px-4", children: [
807
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("p", { className: "text-sm font-semibold uppercase tracking-widest mb-3 opacity-80", children: "Category" }),
808
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("h1", { className: "text-4xl md:text-5xl font-bold mb-3", children: categoryName }),
809
+ description.long && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("p", { className: "text-lg opacity-90 max-w-2xl mx-auto mt-2", children: description.long }),
810
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("p", { className: "text-lg opacity-70 mt-2", children: [
721
811
  articles.length,
722
812
  " article",
723
813
  articles.length === 1 ? "" : "s"
724
814
  ] })
725
815
  ] })
726
816
  ] }),
727
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("section", { className: "py-16 bg-muted/50 flex-1", children: /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "max-w-7xl mx-auto px-4 sm:px-6 lg:px-8", children: [
728
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "mb-8 flex items-center justify-between", children: [
729
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_link5.default, { href: "/articles", className: "text-sm text-primary hover:underline", children: "\u2190 All Articles" }),
730
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("p", { className: "text-sm text-muted-foreground", children: description.short })
817
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("section", { className: "py-16 bg-muted/50 flex-1", children: /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "max-w-7xl mx-auto px-4 sm:px-6 lg:px-8", children: [
818
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "mb-8 flex items-center justify-between", children: [
819
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_link6.default, { href: "/articles", className: "text-sm text-primary hover:underline", children: "\u2190 All Articles" }),
820
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("p", { className: "text-sm text-muted-foreground", children: description.short })
731
821
  ] }),
732
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(LatestArticles, { articles, pageSize })
822
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(LatestArticles, { articles, pageSize })
733
823
  ] }) })
734
824
  ] });
735
825
  }
@@ -740,7 +830,7 @@ var import_react7 = require("react");
740
830
 
741
831
  // src/CommentForm.tsx
742
832
  var import_react4 = require("react");
743
- var import_jsx_runtime11 = require("react/jsx-runtime");
833
+ var import_jsx_runtime12 = require("react/jsx-runtime");
744
834
  var MAX_LENGTH = 2e3;
745
835
  var WARN_THRESHOLD = 1800;
746
836
  function submitLabel(submitting, parentId) {
@@ -779,8 +869,8 @@ function CommentForm({ articleSlug, parentId, onSuccess, onCancel }) {
779
869
  }
780
870
  });
781
871
  }
782
- return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("form", { onSubmit: handleSubmit, className: "space-y-2", children: [
783
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
872
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("form", { onSubmit: handleSubmit, className: "space-y-2", children: [
873
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
784
874
  "textarea",
785
875
  {
786
876
  value: body,
@@ -793,13 +883,13 @@ function CommentForm({ articleSlug, parentId, onSuccess, onCancel }) {
793
883
  "aria-label": parentId ? "Reply text" : "Comment text"
794
884
  }
795
885
  ),
796
- remaining <= MAX_LENGTH - WARN_THRESHOLD && /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("p", { className: `text-xs ${remaining < 0 ? "text-destructive" : "text-muted-foreground"}`, children: [
886
+ remaining <= MAX_LENGTH - WARN_THRESHOLD && /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("p", { className: `text-xs ${remaining < 0 ? "text-destructive" : "text-muted-foreground"}`, children: [
797
887
  remaining,
798
888
  " characters remaining"
799
889
  ] }),
800
- error && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("p", { className: "text-xs text-destructive", children: error }),
801
- /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "flex gap-2", children: [
802
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
890
+ error && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("p", { className: "text-xs text-destructive", children: error }),
891
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "flex gap-2", children: [
892
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
803
893
  "button",
804
894
  {
805
895
  type: "submit",
@@ -808,7 +898,7 @@ function CommentForm({ articleSlug, parentId, onSuccess, onCancel }) {
808
898
  children: submitLabel(submitting, parentId)
809
899
  }
810
900
  ),
811
- onCancel && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
901
+ onCancel && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
812
902
  "button",
813
903
  {
814
904
  type: "button",
@@ -823,7 +913,7 @@ function CommentForm({ articleSlug, parentId, onSuccess, onCancel }) {
823
913
 
824
914
  // src/CommentItem.tsx
825
915
  var import_react5 = require("react");
826
- var import_jsx_runtime12 = require("react/jsx-runtime");
916
+ var import_jsx_runtime13 = require("react/jsx-runtime");
827
917
  function relativeTime(iso) {
828
918
  const diff = Date.now() - new Date(iso).getTime();
829
919
  const minutes = Math.floor(diff / 6e4);
@@ -861,15 +951,15 @@ function CommentItem({
861
951
  }
862
952
  });
863
953
  }
864
- return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: `${isReply ? "ml-8 border-l-2 border-border pl-4" : ""}`, children: [
865
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "rounded-lg bg-muted/40 p-3 space-y-1", children: comment.isDeleted ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("p", { className: "text-sm italic text-muted-foreground", children: "Comment removed" }) : /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_jsx_runtime12.Fragment, { children: [
866
- /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "flex items-center justify-between gap-2", children: [
867
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "text-sm font-medium text-foreground", children: comment.authorName }),
868
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "text-xs text-muted-foreground", children: relativeTime(comment.createdAt) })
954
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: `${isReply ? "ml-8 border-l-2 border-border pl-4" : ""}`, children: [
955
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "rounded-lg bg-muted/40 p-3 space-y-1", children: comment.isDeleted ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("p", { className: "text-sm italic text-muted-foreground", children: "Comment removed" }) : /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(import_jsx_runtime13.Fragment, { children: [
956
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "flex items-center justify-between gap-2", children: [
957
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { className: "text-sm font-medium text-foreground", children: comment.authorName }),
958
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { className: "text-xs text-muted-foreground", children: relativeTime(comment.createdAt) })
869
959
  ] }),
870
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("p", { className: "text-sm text-foreground whitespace-pre-wrap break-words", children: comment.body }),
871
- /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "flex gap-3 pt-1", children: [
872
- canReply && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
960
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("p", { className: "text-sm text-foreground whitespace-pre-wrap break-words", children: comment.body }),
961
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "flex gap-3 pt-1", children: [
962
+ canReply && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
873
963
  "button",
874
964
  {
875
965
  onClick: () => setShowReplyForm((v) => !v),
@@ -877,7 +967,7 @@ function CommentItem({
877
967
  children: showReplyForm ? "Cancel" : "Reply"
878
968
  }
879
969
  ),
880
- canDelete && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
970
+ canDelete && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
881
971
  "button",
882
972
  {
883
973
  onClick: handleDelete,
@@ -888,7 +978,7 @@ function CommentItem({
888
978
  )
889
979
  ] })
890
980
  ] }) }),
891
- showReplyForm && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "mt-2 ml-2", children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
981
+ showReplyForm && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "mt-2 ml-2", children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
892
982
  CommentForm,
893
983
  {
894
984
  articleSlug,
@@ -900,7 +990,7 @@ function CommentItem({
900
990
  onCancel: () => setShowReplyForm(false)
901
991
  }
902
992
  ) }),
903
- replies.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "mt-2 space-y-2", children: replies.map((reply) => /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
993
+ replies.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "mt-2 space-y-2", children: replies.map((reply) => /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
904
994
  CommentItem,
905
995
  {
906
996
  comment: __spreadProps(__spreadValues({}, reply), { replies: [] }),
@@ -915,7 +1005,7 @@ function CommentItem({
915
1005
  }
916
1006
 
917
1007
  // src/CommentThread.tsx
918
- var import_jsx_runtime13 = require("react/jsx-runtime");
1008
+ var import_jsx_runtime14 = require("react/jsx-runtime");
919
1009
  function CommentThread({
920
1010
  comments,
921
1011
  articleSlug,
@@ -923,9 +1013,9 @@ function CommentThread({
923
1013
  onRefresh
924
1014
  }) {
925
1015
  if (comments.length === 0) {
926
- return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("p", { className: "text-sm text-muted-foreground text-center py-6", children: "No comments yet. Be the first to share your thoughts." });
1016
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("p", { className: "text-sm text-muted-foreground text-center py-6", children: "No comments yet. Be the first to share your thoughts." });
927
1017
  }
928
- return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "space-y-4", children: comments.map((comment) => /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
1018
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { className: "space-y-4", children: comments.map((comment) => /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
929
1019
  CommentItem,
930
1020
  {
931
1021
  comment,
@@ -938,7 +1028,7 @@ function CommentThread({
938
1028
  }
939
1029
 
940
1030
  // src/CommentsSection.tsx
941
- var import_jsx_runtime14 = require("react/jsx-runtime");
1031
+ var import_jsx_runtime15 = require("react/jsx-runtime");
942
1032
  function CommentsSection({ articleSlug, config }) {
943
1033
  var _a, _b, _c, _d, _e, _f;
944
1034
  const { data: session, status } = (0, import_react6.useSession)();
@@ -970,10 +1060,10 @@ function CommentsSection({ articleSlug, config }) {
970
1060
  if (!enabled) return null;
971
1061
  const count = comments.length;
972
1062
  const label = count === 1 ? "1 comment" : `${count} comments`;
973
- return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("section", { "aria-label": "Comments", className: "mt-12 pt-8 border-t border-border space-y-6", children: [
974
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("h2", { className: "text-xl font-semibold text-foreground", children: loading ? "Comments" : label }),
975
- status === "authenticated" ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(CommentForm, { articleSlug, onSuccess: fetchComments }) : /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("p", { className: "text-sm text-muted-foreground", children: [
976
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
1063
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("section", { "aria-label": "Comments", className: "mt-12 pt-8 border-t border-border space-y-6", children: [
1064
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("h2", { className: "text-xl font-semibold text-foreground", children: loading ? "Comments" : label }),
1065
+ status === "authenticated" ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(CommentForm, { articleSlug, onSuccess: fetchComments }) : /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("p", { className: "text-sm text-muted-foreground", children: [
1066
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
977
1067
  "button",
978
1068
  {
979
1069
  onClick: () => (0, import_react6.signIn)(),
@@ -984,8 +1074,8 @@ function CommentsSection({ articleSlug, config }) {
984
1074
  " ",
985
1075
  "to join the discussion."
986
1076
  ] }),
987
- fetchError && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("p", { className: "text-sm text-destructive", children: fetchError }),
988
- loading ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("p", { className: "text-sm text-muted-foreground", children: "Loading comments\u2026" }) : /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
1077
+ fetchError && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("p", { className: "text-sm text-destructive", children: fetchError }),
1078
+ loading ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("p", { className: "text-sm text-muted-foreground", children: "Loading comments\u2026" }) : /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
989
1079
  CommentThread,
990
1080
  {
991
1081
  comments,
@@ -1000,6 +1090,7 @@ function CommentsSection({ articleSlug, config }) {
1000
1090
  0 && (module.exports = {
1001
1091
  ArticleCard,
1002
1092
  ArticleCategoryGrid,
1093
+ ArticleDetailHero,
1003
1094
  ArticleSchema,
1004
1095
  ArticleSearchBar,
1005
1096
  ArticlesHero,