@fullstackdatasolutions/articles 0.2.1 → 0.4.2
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 +99 -5
- package/dist/index.cjs +379 -106
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +29 -1
- package/dist/index.d.ts +29 -1
- package/dist/index.js +371 -101
- package/dist/index.js.map +1 -1
- package/dist/server.cjs +2 -1
- package/dist/server.cjs.map +1 -1
- package/dist/server.d.cts +1 -1
- package/dist/server.d.ts +1 -1
- package/dist/server.js +2 -1
- package/dist/server.js.map +1 -1
- package/package.json +8 -7
- package/src/ArticleCategoryGrid.tsx +1 -8
- package/src/ArticleDetailHero.tsx +122 -0
- package/src/ArticleNavigation.tsx +56 -0
- package/src/ArticleSocialShare.tsx +96 -0
- package/src/CategoryArticlesPage.tsx +21 -4
- package/src/__tests__/ArticleCategoryGrid.test.tsx +40 -1
- package/src/__tests__/ArticleDetailHero.test.tsx +134 -0
- package/src/__tests__/ArticleNavigation.test.tsx +127 -0
- package/src/__tests__/ArticleSocialShare.test.tsx +187 -0
- package/src/__tests__/ArticlesPage.test.tsx +147 -1
- package/src/__tests__/CommentItem.test.tsx +110 -0
- package/src/__tests__/seoUtils.test.ts +35 -0
- package/src/index.ts +4 -0
- package/src/seoUtils.ts +7 -1
package/dist/index.js
CHANGED
|
@@ -58,37 +58,26 @@ function ArticleCategoryGrid({ categories, pageSize = 8 }) {
|
|
|
58
58
|
/* @__PURE__ */ jsx("h2", { className: "text-3xl font-bold text-foreground mb-4", children: "Browse by Category" }),
|
|
59
59
|
/* @__PURE__ */ jsx("p", { className: "text-lg text-muted-foreground", children: "Explore our articles by topic to find the insights most relevant to your interests." })
|
|
60
60
|
] }),
|
|
61
|
-
/* @__PURE__ */ jsx(
|
|
62
|
-
"div",
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
/* @__PURE__ */ jsxs("div", { className: "p-4 text-center", children: [
|
|
82
|
-
/* @__PURE__ */ jsx("h3", { className: "font-semibold text-foreground text-base", children: cat.name }),
|
|
83
|
-
/* @__PURE__ */ jsxs("p", { className: "text-sm text-muted-foreground mt-1", children: [
|
|
84
|
-
cat.count,
|
|
85
|
-
" ",
|
|
86
|
-
cat.count === 1 ? "article" : "articles"
|
|
87
|
-
] })
|
|
88
|
-
] })
|
|
89
|
-
] }) }, cat.slug))
|
|
90
|
-
}
|
|
91
|
-
),
|
|
61
|
+
/* @__PURE__ */ jsx("div", { className: "grid md:grid-cols-2 lg:grid-cols-3 gap-6", children: visible.map((cat) => /* @__PURE__ */ jsx(Link, { href: `/articles/category/${cat.slug}`, children: /* @__PURE__ */ 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: [
|
|
62
|
+
/* @__PURE__ */ jsx("div", { className: "overflow-hidden", style: { position: "relative", height: "13rem" }, children: /* @__PURE__ */ jsx(
|
|
63
|
+
Image,
|
|
64
|
+
{
|
|
65
|
+
src: cat.featuredImage,
|
|
66
|
+
alt: cat.name,
|
|
67
|
+
fill: true,
|
|
68
|
+
sizes: "(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw",
|
|
69
|
+
className: "object-cover group-hover:scale-105 transition-transform duration-300"
|
|
70
|
+
}
|
|
71
|
+
) }),
|
|
72
|
+
/* @__PURE__ */ jsxs("div", { className: "p-4 text-center", children: [
|
|
73
|
+
/* @__PURE__ */ jsx("h3", { className: "font-semibold text-foreground text-base", children: cat.name }),
|
|
74
|
+
/* @__PURE__ */ jsxs("p", { className: "text-sm text-muted-foreground mt-1", children: [
|
|
75
|
+
cat.count,
|
|
76
|
+
" ",
|
|
77
|
+
cat.count === 1 ? "article" : "articles"
|
|
78
|
+
] })
|
|
79
|
+
] })
|
|
80
|
+
] }) }, cat.slug)) }),
|
|
92
81
|
hasMore && /* @__PURE__ */ jsx("div", { className: "mt-10 text-center", children: /* @__PURE__ */ jsx(
|
|
93
82
|
"button",
|
|
94
83
|
{
|
|
@@ -618,10 +607,140 @@ function ArticlesPage({ config }) {
|
|
|
618
607
|
] });
|
|
619
608
|
}
|
|
620
609
|
|
|
621
|
-
// src/
|
|
610
|
+
// src/ArticleDetailHero.tsx
|
|
622
611
|
import Image4 from "next/image";
|
|
623
612
|
import Link5 from "next/link";
|
|
613
|
+
import { Calendar as Calendar3, Clock as Clock3, User as User2 } from "lucide-react";
|
|
624
614
|
import { jsx as jsx10, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
615
|
+
function toSlug(cat) {
|
|
616
|
+
return cat.toLowerCase().replaceAll(/\s+/g, "-").replaceAll(/[^a-z0-9-]/g, "");
|
|
617
|
+
}
|
|
618
|
+
function ArticleDetailHero({
|
|
619
|
+
article,
|
|
620
|
+
categoryBasePath = "/articles/category",
|
|
621
|
+
showDate = false
|
|
622
|
+
}) {
|
|
623
|
+
return /* @__PURE__ */ jsxs9(
|
|
624
|
+
"section",
|
|
625
|
+
{
|
|
626
|
+
style: {
|
|
627
|
+
position: "relative",
|
|
628
|
+
height: "18rem",
|
|
629
|
+
display: "flex",
|
|
630
|
+
alignItems: "center",
|
|
631
|
+
justifyContent: "center",
|
|
632
|
+
overflow: "hidden"
|
|
633
|
+
},
|
|
634
|
+
children: [
|
|
635
|
+
/* @__PURE__ */ jsx10(
|
|
636
|
+
Image4,
|
|
637
|
+
{
|
|
638
|
+
src: article.featuredImage,
|
|
639
|
+
alt: article.title,
|
|
640
|
+
fill: true,
|
|
641
|
+
sizes: "100vw",
|
|
642
|
+
className: "object-cover",
|
|
643
|
+
style: { objectFit: "cover" },
|
|
644
|
+
priority: true
|
|
645
|
+
}
|
|
646
|
+
),
|
|
647
|
+
/* @__PURE__ */ jsx10(
|
|
648
|
+
"div",
|
|
649
|
+
{
|
|
650
|
+
style: {
|
|
651
|
+
position: "absolute",
|
|
652
|
+
inset: 0,
|
|
653
|
+
backgroundColor: "rgba(0,0,0,0.6)"
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
),
|
|
657
|
+
/* @__PURE__ */ jsxs9(
|
|
658
|
+
"div",
|
|
659
|
+
{
|
|
660
|
+
style: {
|
|
661
|
+
position: "relative",
|
|
662
|
+
zIndex: 10,
|
|
663
|
+
textAlign: "center",
|
|
664
|
+
color: "white",
|
|
665
|
+
padding: "0 1rem",
|
|
666
|
+
maxWidth: "56rem",
|
|
667
|
+
margin: "0 auto",
|
|
668
|
+
width: "100%"
|
|
669
|
+
},
|
|
670
|
+
children: [
|
|
671
|
+
article.categories && article.categories.length > 0 && /* @__PURE__ */ jsx10(
|
|
672
|
+
"div",
|
|
673
|
+
{
|
|
674
|
+
style: {
|
|
675
|
+
display: "flex",
|
|
676
|
+
flexWrap: "wrap",
|
|
677
|
+
justifyContent: "center",
|
|
678
|
+
gap: "0.5rem",
|
|
679
|
+
marginBottom: "1rem"
|
|
680
|
+
},
|
|
681
|
+
children: article.categories.slice(0, 4).map(
|
|
682
|
+
(cat) => categoryBasePath ? /* @__PURE__ */ jsx10(
|
|
683
|
+
Link5,
|
|
684
|
+
{
|
|
685
|
+
href: `${categoryBasePath}/${toSlug(cat)}`,
|
|
686
|
+
className: "inline-block bg-white/20 text-white text-sm font-medium px-3 py-1 rounded-full hover:bg-white/30 transition-colors",
|
|
687
|
+
children: cat
|
|
688
|
+
},
|
|
689
|
+
cat
|
|
690
|
+
) : /* @__PURE__ */ jsx10(
|
|
691
|
+
"span",
|
|
692
|
+
{
|
|
693
|
+
className: "inline-block bg-white/20 text-white text-sm font-medium px-3 py-1 rounded-full",
|
|
694
|
+
children: cat
|
|
695
|
+
},
|
|
696
|
+
cat
|
|
697
|
+
)
|
|
698
|
+
)
|
|
699
|
+
}
|
|
700
|
+
),
|
|
701
|
+
/* @__PURE__ */ jsx10("h1", { className: "text-4xl md:text-5xl font-bold mb-4", children: article.title }),
|
|
702
|
+
/* @__PURE__ */ jsxs9(
|
|
703
|
+
"div",
|
|
704
|
+
{
|
|
705
|
+
style: {
|
|
706
|
+
display: "flex",
|
|
707
|
+
alignItems: "center",
|
|
708
|
+
justifyContent: "center",
|
|
709
|
+
gap: "1.5rem",
|
|
710
|
+
fontSize: "0.875rem",
|
|
711
|
+
color: "rgba(255,255,255,0.8)"
|
|
712
|
+
},
|
|
713
|
+
children: [
|
|
714
|
+
showDate && article.date && /* @__PURE__ */ jsxs9("div", { style: { display: "flex", alignItems: "center", gap: "0.5rem" }, children: [
|
|
715
|
+
/* @__PURE__ */ jsx10(Calendar3, { className: "h-4 w-4" }),
|
|
716
|
+
/* @__PURE__ */ jsx10("span", { children: article.date })
|
|
717
|
+
] }),
|
|
718
|
+
/* @__PURE__ */ jsxs9("div", { style: { display: "flex", alignItems: "center", gap: "0.5rem" }, children: [
|
|
719
|
+
/* @__PURE__ */ jsx10(Clock3, { className: "h-4 w-4" }),
|
|
720
|
+
/* @__PURE__ */ jsx10("span", { children: article.readTime })
|
|
721
|
+
] }),
|
|
722
|
+
/* @__PURE__ */ jsxs9("div", { style: { display: "flex", alignItems: "center", gap: "0.5rem" }, children: [
|
|
723
|
+
/* @__PURE__ */ jsx10(User2, { className: "h-4 w-4" }),
|
|
724
|
+
/* @__PURE__ */ jsxs9("span", { children: [
|
|
725
|
+
"By ",
|
|
726
|
+
article.author
|
|
727
|
+
] })
|
|
728
|
+
] })
|
|
729
|
+
]
|
|
730
|
+
}
|
|
731
|
+
)
|
|
732
|
+
]
|
|
733
|
+
}
|
|
734
|
+
)
|
|
735
|
+
]
|
|
736
|
+
}
|
|
737
|
+
);
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
// src/CategoryArticlesPage.tsx
|
|
741
|
+
import Image5 from "next/image";
|
|
742
|
+
import Link6 from "next/link";
|
|
743
|
+
import { jsx as jsx11, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
625
744
|
function getCategoryDescription(config, slug, categoryName) {
|
|
626
745
|
var _a;
|
|
627
746
|
const raw = (_a = config.categoryDescriptions) == null ? void 0 : _a[slug];
|
|
@@ -637,8 +756,8 @@ function CategoryArticlesPage({ category, articles, config }) {
|
|
|
637
756
|
const description = getCategoryDescription(config, category, categoryName);
|
|
638
757
|
const pageSize = (_a = config.pageSize) != null ? _a : DEFAULT_PAGE_SIZE;
|
|
639
758
|
const siteUrl = config.siteUrl.replace(/\/$/, "");
|
|
640
|
-
return /* @__PURE__ */
|
|
641
|
-
/* @__PURE__ */
|
|
759
|
+
return /* @__PURE__ */ jsxs10("div", { children: [
|
|
760
|
+
/* @__PURE__ */ jsx11(
|
|
642
761
|
BreadcrumbSchema,
|
|
643
762
|
{
|
|
644
763
|
items: [
|
|
@@ -648,47 +767,195 @@ function CategoryArticlesPage({ category, articles, config }) {
|
|
|
648
767
|
]
|
|
649
768
|
}
|
|
650
769
|
),
|
|
651
|
-
/* @__PURE__ */
|
|
652
|
-
|
|
653
|
-
|
|
770
|
+
/* @__PURE__ */ jsxs10(
|
|
771
|
+
"section",
|
|
772
|
+
{
|
|
773
|
+
style: {
|
|
774
|
+
position: "relative",
|
|
775
|
+
height: "20rem",
|
|
776
|
+
display: "flex",
|
|
777
|
+
alignItems: "center",
|
|
778
|
+
justifyContent: "center",
|
|
779
|
+
overflow: "hidden"
|
|
780
|
+
},
|
|
781
|
+
children: [
|
|
782
|
+
/* @__PURE__ */ jsx11(
|
|
783
|
+
Image5,
|
|
784
|
+
{
|
|
785
|
+
src: heroImage,
|
|
786
|
+
alt: categoryName,
|
|
787
|
+
fill: true,
|
|
788
|
+
sizes: "100vw",
|
|
789
|
+
style: { objectFit: "cover" },
|
|
790
|
+
priority: true
|
|
791
|
+
}
|
|
792
|
+
),
|
|
793
|
+
/* @__PURE__ */ jsx11("div", { style: { position: "absolute", inset: 0, backgroundColor: "rgba(0,0,0,0.6)" } }),
|
|
794
|
+
/* @__PURE__ */ jsxs10(
|
|
795
|
+
"div",
|
|
796
|
+
{
|
|
797
|
+
style: {
|
|
798
|
+
position: "relative",
|
|
799
|
+
zIndex: 10,
|
|
800
|
+
textAlign: "center",
|
|
801
|
+
color: "white",
|
|
802
|
+
padding: "0 1rem"
|
|
803
|
+
},
|
|
804
|
+
children: [
|
|
805
|
+
/* @__PURE__ */ jsx11("p", { className: "text-sm font-semibold uppercase tracking-widest mb-3 opacity-80", children: "Category" }),
|
|
806
|
+
/* @__PURE__ */ jsx11("h1", { className: "text-4xl md:text-5xl font-bold mb-3", children: categoryName }),
|
|
807
|
+
description.long && /* @__PURE__ */ jsx11("p", { className: "text-lg opacity-90 max-w-2xl mx-auto mt-2", children: description.long }),
|
|
808
|
+
/* @__PURE__ */ jsxs10("p", { className: "text-lg opacity-70 mt-2", children: [
|
|
809
|
+
articles.length,
|
|
810
|
+
" article",
|
|
811
|
+
articles.length === 1 ? "" : "s"
|
|
812
|
+
] })
|
|
813
|
+
]
|
|
814
|
+
}
|
|
815
|
+
)
|
|
816
|
+
]
|
|
817
|
+
}
|
|
818
|
+
),
|
|
819
|
+
/* @__PURE__ */ jsx11("section", { className: "py-16 bg-muted/50 flex-1", children: /* @__PURE__ */ jsxs10("div", { className: "max-w-7xl mx-auto px-4 sm:px-6 lg:px-8", children: [
|
|
820
|
+
/* @__PURE__ */ jsxs10("div", { className: "mb-8 flex items-center justify-between", children: [
|
|
821
|
+
/* @__PURE__ */ jsx11(Link6, { href: "/articles", className: "text-sm text-primary hover:underline", children: "\u2190 All Articles" }),
|
|
822
|
+
/* @__PURE__ */ jsx11("p", { className: "text-sm text-muted-foreground", children: description.short })
|
|
823
|
+
] }),
|
|
824
|
+
/* @__PURE__ */ jsx11(LatestArticles, { articles, pageSize })
|
|
825
|
+
] }) })
|
|
826
|
+
] });
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
// src/ArticleSocialShare.tsx
|
|
830
|
+
import { useState as useState4 } from "react";
|
|
831
|
+
import { Share2, Copy, Check, Mail, MessageCircle, Users, FileText } from "lucide-react";
|
|
832
|
+
import { jsx as jsx12, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
833
|
+
function ArticleSocialShare({ title, url, excerpt, shareMessage }) {
|
|
834
|
+
const [copied, setCopied] = useState4(false);
|
|
835
|
+
const encodedTitle = encodeURIComponent(title);
|
|
836
|
+
const encodedUrl = encodeURIComponent(url);
|
|
837
|
+
const encodedExcerpt = encodeURIComponent(excerpt || "");
|
|
838
|
+
const shareLinks = {
|
|
839
|
+
linkedin: `https://www.linkedin.com/sharing/share-offsite/?url=${encodedUrl}`,
|
|
840
|
+
facebook: `https://www.facebook.com/sharer/sharer.php?u=${encodedUrl}`,
|
|
841
|
+
twitter: `https://twitter.com/intent/tweet?text=${encodedTitle}&url=${encodedUrl}`,
|
|
842
|
+
reddit: `https://reddit.com/submit?url=${encodedUrl}&title=${encodedTitle}`,
|
|
843
|
+
email: `mailto:?subject=${encodedTitle}&body=${encodedExcerpt}%0A%0A${encodedUrl}`,
|
|
844
|
+
whatsapp: `https://wa.me/?text=${encodedTitle}%20${encodedUrl}`,
|
|
845
|
+
telegram: `https://t.me/share/url?url=${encodedUrl}&text=${encodedTitle}`
|
|
846
|
+
};
|
|
847
|
+
const copyToClipboard = () => __async(this, null, function* () {
|
|
848
|
+
try {
|
|
849
|
+
yield navigator.clipboard.writeText(url);
|
|
850
|
+
setCopied(true);
|
|
851
|
+
setTimeout(() => setCopied(false), 2e3);
|
|
852
|
+
} catch (e) {
|
|
853
|
+
}
|
|
854
|
+
});
|
|
855
|
+
const openShareWindow = (shareUrl) => {
|
|
856
|
+
window.open(shareUrl, "_blank", "width=600,height=400,scrollbars=yes,resizable=yes");
|
|
857
|
+
};
|
|
858
|
+
const btnClass = "inline-flex items-center gap-2 px-3 py-1.5 text-sm rounded-md border border-border bg-background text-foreground hover:bg-primary/10 hover:border-primary hover:text-primary transition-colors cursor-pointer";
|
|
859
|
+
return /* @__PURE__ */ jsxs11("div", { className: "border-t border-border pt-8 mt-8", children: [
|
|
860
|
+
/* @__PURE__ */ jsxs11("div", { className: "flex items-center gap-2 mb-4", children: [
|
|
861
|
+
/* @__PURE__ */ jsx12(Share2, { className: "h-5 w-5 text-muted-foreground" }),
|
|
862
|
+
/* @__PURE__ */ jsx12("h3", { className: "text-lg font-semibold text-foreground", children: "Share this article" })
|
|
863
|
+
] }),
|
|
864
|
+
/* @__PURE__ */ jsxs11("div", { className: "flex flex-wrap gap-2", children: [
|
|
865
|
+
/* @__PURE__ */ jsxs11("button", { className: btnClass, onClick: () => openShareWindow(shareLinks.linkedin), children: [
|
|
866
|
+
/* @__PURE__ */ jsx12(Users, { className: "h-4 w-4" }),
|
|
867
|
+
"LinkedIn"
|
|
868
|
+
] }),
|
|
869
|
+
/* @__PURE__ */ jsxs11("button", { className: btnClass, onClick: () => openShareWindow(shareLinks.facebook), children: [
|
|
870
|
+
/* @__PURE__ */ jsx12("span", { className: "text-xs font-bold", children: "f" }),
|
|
871
|
+
" Facebook"
|
|
872
|
+
] }),
|
|
873
|
+
/* @__PURE__ */ jsxs11("button", { className: btnClass, onClick: () => openShareWindow(shareLinks.twitter), children: [
|
|
874
|
+
/* @__PURE__ */ jsx12(MessageCircle, { className: "h-4 w-4" }),
|
|
875
|
+
"Twitter"
|
|
876
|
+
] }),
|
|
877
|
+
/* @__PURE__ */ jsxs11("button", { className: btnClass, onClick: () => openShareWindow(shareLinks.reddit), children: [
|
|
878
|
+
/* @__PURE__ */ jsx12(FileText, { className: "h-4 w-4" }),
|
|
879
|
+
"Reddit"
|
|
880
|
+
] }),
|
|
881
|
+
/* @__PURE__ */ jsxs11("button", { className: btnClass, onClick: () => openShareWindow(shareLinks.whatsapp), children: [
|
|
882
|
+
/* @__PURE__ */ jsx12(MessageCircle, { className: "h-4 w-4" }),
|
|
883
|
+
"WhatsApp"
|
|
884
|
+
] }),
|
|
885
|
+
/* @__PURE__ */ jsxs11("button", { className: btnClass, onClick: () => openShareWindow(shareLinks.telegram), children: [
|
|
886
|
+
/* @__PURE__ */ jsx12(MessageCircle, { className: "h-4 w-4" }),
|
|
887
|
+
"Telegram"
|
|
888
|
+
] }),
|
|
889
|
+
/* @__PURE__ */ jsxs11(
|
|
890
|
+
"button",
|
|
654
891
|
{
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
892
|
+
className: btnClass,
|
|
893
|
+
onClick: () => {
|
|
894
|
+
globalThis.location.href = shareLinks.email;
|
|
895
|
+
},
|
|
896
|
+
children: [
|
|
897
|
+
/* @__PURE__ */ jsx12(Mail, { className: "h-4 w-4" }),
|
|
898
|
+
"Email"
|
|
899
|
+
]
|
|
661
900
|
}
|
|
662
901
|
),
|
|
663
|
-
/* @__PURE__ */
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
/* @__PURE__ */ jsx10("h1", { className: "text-4xl md:text-5xl font-bold mb-3", children: categoryName }),
|
|
667
|
-
description.long && /* @__PURE__ */ jsx10("p", { className: "text-lg opacity-90 max-w-2xl mx-auto mt-2", children: description.long }),
|
|
668
|
-
/* @__PURE__ */ jsxs9("p", { className: "text-lg opacity-70 mt-2", children: [
|
|
669
|
-
articles.length,
|
|
670
|
-
" article",
|
|
671
|
-
articles.length === 1 ? "" : "s"
|
|
672
|
-
] })
|
|
902
|
+
/* @__PURE__ */ jsxs11("button", { className: btnClass, onClick: copyToClipboard, children: [
|
|
903
|
+
copied ? /* @__PURE__ */ jsx12(Check, { className: "h-4 w-4" }) : /* @__PURE__ */ jsx12(Copy, { className: "h-4 w-4" }),
|
|
904
|
+
copied ? "Copied!" : "Copy Link"
|
|
673
905
|
] })
|
|
674
906
|
] }),
|
|
675
|
-
|
|
676
|
-
/* @__PURE__ */ jsxs9("div", { className: "mb-8 flex items-center justify-between", children: [
|
|
677
|
-
/* @__PURE__ */ jsx10(Link5, { href: "/articles", className: "text-sm text-primary hover:underline", children: "\u2190 All Articles" }),
|
|
678
|
-
/* @__PURE__ */ jsx10("p", { className: "text-sm text-muted-foreground", children: description.short })
|
|
679
|
-
] }),
|
|
680
|
-
/* @__PURE__ */ jsx10(LatestArticles, { articles, pageSize })
|
|
681
|
-
] }) })
|
|
907
|
+
shareMessage && /* @__PURE__ */ jsx12("p", { className: "text-sm text-muted-foreground mt-3", children: shareMessage })
|
|
682
908
|
] });
|
|
683
909
|
}
|
|
684
910
|
|
|
911
|
+
// src/ArticleNavigation.tsx
|
|
912
|
+
import Link7 from "next/link";
|
|
913
|
+
import { ChevronLeft, ChevronRight } from "lucide-react";
|
|
914
|
+
import { jsx as jsx13, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
915
|
+
function truncateTitle(title, maxLength = 60) {
|
|
916
|
+
return title.length > maxLength ? `${title.substring(0, maxLength)}...` : title;
|
|
917
|
+
}
|
|
918
|
+
function ArticleNavigation({ previous, next, basePath }) {
|
|
919
|
+
if (!previous && !next) return null;
|
|
920
|
+
return /* @__PURE__ */ jsx13("nav", { className: "mt-12 pt-8 border-t border-border", "aria-label": "Article navigation", children: /* @__PURE__ */ jsxs12("div", { className: "flex justify-between gap-4", children: [
|
|
921
|
+
/* @__PURE__ */ jsx13("div", { className: "flex-1 min-w-0", children: previous && /* @__PURE__ */ jsxs12(
|
|
922
|
+
Link7,
|
|
923
|
+
{
|
|
924
|
+
href: `${basePath}/${previous.slug}`,
|
|
925
|
+
className: "group flex items-center gap-3 p-4 rounded-lg border border-border hover:border-primary/20 hover:bg-muted/50 transition-colors",
|
|
926
|
+
children: [
|
|
927
|
+
/* @__PURE__ */ jsx13(ChevronLeft, { className: "h-5 w-5 text-muted-foreground group-hover:text-primary shrink-0" }),
|
|
928
|
+
/* @__PURE__ */ jsxs12("div", { className: "min-w-0 flex-1", children: [
|
|
929
|
+
/* @__PURE__ */ jsx13("div", { className: "text-sm text-muted-foreground mb-1", children: "Previous Article" }),
|
|
930
|
+
/* @__PURE__ */ jsx13("div", { className: "font-medium text-foreground truncate", title: previous.title, children: truncateTitle(previous.title) })
|
|
931
|
+
] })
|
|
932
|
+
]
|
|
933
|
+
}
|
|
934
|
+
) }),
|
|
935
|
+
/* @__PURE__ */ jsx13("div", { className: "flex-1 min-w-0", children: next && /* @__PURE__ */ jsxs12(
|
|
936
|
+
Link7,
|
|
937
|
+
{
|
|
938
|
+
href: `${basePath}/${next.slug}`,
|
|
939
|
+
className: "group flex items-center justify-end gap-3 p-4 rounded-lg border border-border hover:border-primary/20 hover:bg-muted/50 transition-colors",
|
|
940
|
+
children: [
|
|
941
|
+
/* @__PURE__ */ jsxs12("div", { className: "min-w-0 flex-1 text-right", children: [
|
|
942
|
+
/* @__PURE__ */ jsx13("div", { className: "text-sm text-muted-foreground mb-1", children: "Next Article" }),
|
|
943
|
+
/* @__PURE__ */ jsx13("div", { className: "font-medium text-foreground truncate", title: next.title, children: truncateTitle(next.title) })
|
|
944
|
+
] }),
|
|
945
|
+
/* @__PURE__ */ jsx13(ChevronRight, { className: "h-5 w-5 text-muted-foreground group-hover:text-primary shrink-0" })
|
|
946
|
+
]
|
|
947
|
+
}
|
|
948
|
+
) })
|
|
949
|
+
] }) });
|
|
950
|
+
}
|
|
951
|
+
|
|
685
952
|
// src/CommentsSection.tsx
|
|
686
953
|
import { useSession, signIn } from "next-auth/react";
|
|
687
|
-
import { useCallback as useCallback2, useEffect as useEffect4, useState as
|
|
954
|
+
import { useCallback as useCallback2, useEffect as useEffect4, useState as useState7 } from "react";
|
|
688
955
|
|
|
689
956
|
// src/CommentForm.tsx
|
|
690
|
-
import { useState as
|
|
691
|
-
import { jsx as
|
|
957
|
+
import { useState as useState5 } from "react";
|
|
958
|
+
import { jsx as jsx14, jsxs as jsxs13 } from "react/jsx-runtime";
|
|
692
959
|
var MAX_LENGTH = 2e3;
|
|
693
960
|
var WARN_THRESHOLD = 1800;
|
|
694
961
|
function submitLabel(submitting, parentId) {
|
|
@@ -696,9 +963,9 @@ function submitLabel(submitting, parentId) {
|
|
|
696
963
|
return parentId ? "Reply" : "Post comment";
|
|
697
964
|
}
|
|
698
965
|
function CommentForm({ articleSlug, parentId, onSuccess, onCancel }) {
|
|
699
|
-
const [body, setBody] =
|
|
700
|
-
const [submitting, setSubmitting] =
|
|
701
|
-
const [error, setError] =
|
|
966
|
+
const [body, setBody] = useState5("");
|
|
967
|
+
const [submitting, setSubmitting] = useState5(false);
|
|
968
|
+
const [error, setError] = useState5(null);
|
|
702
969
|
const remaining = MAX_LENGTH - body.length;
|
|
703
970
|
function handleSubmit(e) {
|
|
704
971
|
return __async(this, null, function* () {
|
|
@@ -727,8 +994,8 @@ function CommentForm({ articleSlug, parentId, onSuccess, onCancel }) {
|
|
|
727
994
|
}
|
|
728
995
|
});
|
|
729
996
|
}
|
|
730
|
-
return /* @__PURE__ */
|
|
731
|
-
/* @__PURE__ */
|
|
997
|
+
return /* @__PURE__ */ jsxs13("form", { onSubmit: handleSubmit, className: "space-y-2", children: [
|
|
998
|
+
/* @__PURE__ */ jsx14(
|
|
732
999
|
"textarea",
|
|
733
1000
|
{
|
|
734
1001
|
value: body,
|
|
@@ -741,13 +1008,13 @@ function CommentForm({ articleSlug, parentId, onSuccess, onCancel }) {
|
|
|
741
1008
|
"aria-label": parentId ? "Reply text" : "Comment text"
|
|
742
1009
|
}
|
|
743
1010
|
),
|
|
744
|
-
remaining <= MAX_LENGTH - WARN_THRESHOLD && /* @__PURE__ */
|
|
1011
|
+
remaining <= MAX_LENGTH - WARN_THRESHOLD && /* @__PURE__ */ jsxs13("p", { className: `text-xs ${remaining < 0 ? "text-destructive" : "text-muted-foreground"}`, children: [
|
|
745
1012
|
remaining,
|
|
746
1013
|
" characters remaining"
|
|
747
1014
|
] }),
|
|
748
|
-
error && /* @__PURE__ */
|
|
749
|
-
/* @__PURE__ */
|
|
750
|
-
/* @__PURE__ */
|
|
1015
|
+
error && /* @__PURE__ */ jsx14("p", { className: "text-xs text-destructive", children: error }),
|
|
1016
|
+
/* @__PURE__ */ jsxs13("div", { className: "flex gap-2", children: [
|
|
1017
|
+
/* @__PURE__ */ jsx14(
|
|
751
1018
|
"button",
|
|
752
1019
|
{
|
|
753
1020
|
type: "submit",
|
|
@@ -756,7 +1023,7 @@ function CommentForm({ articleSlug, parentId, onSuccess, onCancel }) {
|
|
|
756
1023
|
children: submitLabel(submitting, parentId)
|
|
757
1024
|
}
|
|
758
1025
|
),
|
|
759
|
-
onCancel && /* @__PURE__ */
|
|
1026
|
+
onCancel && /* @__PURE__ */ jsx14(
|
|
760
1027
|
"button",
|
|
761
1028
|
{
|
|
762
1029
|
type: "button",
|
|
@@ -770,8 +1037,8 @@ function CommentForm({ articleSlug, parentId, onSuccess, onCancel }) {
|
|
|
770
1037
|
}
|
|
771
1038
|
|
|
772
1039
|
// src/CommentItem.tsx
|
|
773
|
-
import { useState as
|
|
774
|
-
import { Fragment, jsx as
|
|
1040
|
+
import { useState as useState6 } from "react";
|
|
1041
|
+
import { Fragment, jsx as jsx15, jsxs as jsxs14 } from "react/jsx-runtime";
|
|
775
1042
|
function relativeTime(iso) {
|
|
776
1043
|
const diff = Date.now() - new Date(iso).getTime();
|
|
777
1044
|
const minutes = Math.floor(diff / 6e4);
|
|
@@ -792,8 +1059,8 @@ function CommentItem({
|
|
|
792
1059
|
isReply = false,
|
|
793
1060
|
onRefresh
|
|
794
1061
|
}) {
|
|
795
|
-
const [showReplyForm, setShowReplyForm] =
|
|
796
|
-
const [deleting, setDeleting] =
|
|
1062
|
+
const [showReplyForm, setShowReplyForm] = useState6(false);
|
|
1063
|
+
const [deleting, setDeleting] = useState6(false);
|
|
797
1064
|
const replies = "replies" in comment ? comment.replies : [];
|
|
798
1065
|
const canReply = !isReply && !!currentUserId && !comment.isDeleted;
|
|
799
1066
|
const canDelete = !!currentUserId && currentUserId === comment.authorId && !comment.isDeleted;
|
|
@@ -809,15 +1076,15 @@ function CommentItem({
|
|
|
809
1076
|
}
|
|
810
1077
|
});
|
|
811
1078
|
}
|
|
812
|
-
return /* @__PURE__ */
|
|
813
|
-
/* @__PURE__ */
|
|
814
|
-
/* @__PURE__ */
|
|
815
|
-
/* @__PURE__ */
|
|
816
|
-
/* @__PURE__ */
|
|
1079
|
+
return /* @__PURE__ */ jsxs14("div", { className: `${isReply ? "ml-8 border-l-2 border-border pl-4" : ""}`, children: [
|
|
1080
|
+
/* @__PURE__ */ jsx15("div", { className: "rounded-lg bg-muted/40 p-3 space-y-1", children: comment.isDeleted ? /* @__PURE__ */ jsx15("p", { className: "text-sm italic text-muted-foreground", children: "Comment removed" }) : /* @__PURE__ */ jsxs14(Fragment, { children: [
|
|
1081
|
+
/* @__PURE__ */ jsxs14("div", { className: "flex items-center justify-between gap-2", children: [
|
|
1082
|
+
/* @__PURE__ */ jsx15("span", { className: "text-sm font-medium text-foreground", children: comment.authorName }),
|
|
1083
|
+
/* @__PURE__ */ jsx15("span", { className: "text-xs text-muted-foreground", children: relativeTime(comment.createdAt) })
|
|
817
1084
|
] }),
|
|
818
|
-
/* @__PURE__ */
|
|
819
|
-
/* @__PURE__ */
|
|
820
|
-
canReply && /* @__PURE__ */
|
|
1085
|
+
/* @__PURE__ */ jsx15("p", { className: "text-sm text-foreground whitespace-pre-wrap break-words", children: comment.body }),
|
|
1086
|
+
/* @__PURE__ */ jsxs14("div", { className: "flex gap-3 pt-1", children: [
|
|
1087
|
+
canReply && /* @__PURE__ */ jsx15(
|
|
821
1088
|
"button",
|
|
822
1089
|
{
|
|
823
1090
|
onClick: () => setShowReplyForm((v) => !v),
|
|
@@ -825,7 +1092,7 @@ function CommentItem({
|
|
|
825
1092
|
children: showReplyForm ? "Cancel" : "Reply"
|
|
826
1093
|
}
|
|
827
1094
|
),
|
|
828
|
-
canDelete && /* @__PURE__ */
|
|
1095
|
+
canDelete && /* @__PURE__ */ jsx15(
|
|
829
1096
|
"button",
|
|
830
1097
|
{
|
|
831
1098
|
onClick: handleDelete,
|
|
@@ -836,7 +1103,7 @@ function CommentItem({
|
|
|
836
1103
|
)
|
|
837
1104
|
] })
|
|
838
1105
|
] }) }),
|
|
839
|
-
showReplyForm && /* @__PURE__ */
|
|
1106
|
+
showReplyForm && /* @__PURE__ */ jsx15("div", { className: "mt-2 ml-2", children: /* @__PURE__ */ jsx15(
|
|
840
1107
|
CommentForm,
|
|
841
1108
|
{
|
|
842
1109
|
articleSlug,
|
|
@@ -848,7 +1115,7 @@ function CommentItem({
|
|
|
848
1115
|
onCancel: () => setShowReplyForm(false)
|
|
849
1116
|
}
|
|
850
1117
|
) }),
|
|
851
|
-
replies.length > 0 && /* @__PURE__ */
|
|
1118
|
+
replies.length > 0 && /* @__PURE__ */ jsx15("div", { className: "mt-2 space-y-2", children: replies.map((reply) => /* @__PURE__ */ jsx15(
|
|
852
1119
|
CommentItem,
|
|
853
1120
|
{
|
|
854
1121
|
comment: __spreadProps(__spreadValues({}, reply), { replies: [] }),
|
|
@@ -863,7 +1130,7 @@ function CommentItem({
|
|
|
863
1130
|
}
|
|
864
1131
|
|
|
865
1132
|
// src/CommentThread.tsx
|
|
866
|
-
import { jsx as
|
|
1133
|
+
import { jsx as jsx16 } from "react/jsx-runtime";
|
|
867
1134
|
function CommentThread({
|
|
868
1135
|
comments,
|
|
869
1136
|
articleSlug,
|
|
@@ -871,9 +1138,9 @@ function CommentThread({
|
|
|
871
1138
|
onRefresh
|
|
872
1139
|
}) {
|
|
873
1140
|
if (comments.length === 0) {
|
|
874
|
-
return /* @__PURE__ */
|
|
1141
|
+
return /* @__PURE__ */ jsx16("p", { className: "text-sm text-muted-foreground text-center py-6", children: "No comments yet. Be the first to share your thoughts." });
|
|
875
1142
|
}
|
|
876
|
-
return /* @__PURE__ */
|
|
1143
|
+
return /* @__PURE__ */ jsx16("div", { className: "space-y-4", children: comments.map((comment) => /* @__PURE__ */ jsx16(
|
|
877
1144
|
CommentItem,
|
|
878
1145
|
{
|
|
879
1146
|
comment,
|
|
@@ -886,13 +1153,13 @@ function CommentThread({
|
|
|
886
1153
|
}
|
|
887
1154
|
|
|
888
1155
|
// src/CommentsSection.tsx
|
|
889
|
-
import { jsx as
|
|
1156
|
+
import { jsx as jsx17, jsxs as jsxs15 } from "react/jsx-runtime";
|
|
890
1157
|
function CommentsSection({ articleSlug, config }) {
|
|
891
1158
|
var _a, _b, _c, _d, _e, _f;
|
|
892
1159
|
const { data: session, status } = useSession();
|
|
893
|
-
const [comments, setComments] =
|
|
894
|
-
const [loading, setLoading] =
|
|
895
|
-
const [fetchError, setFetchError] =
|
|
1160
|
+
const [comments, setComments] = useState7([]);
|
|
1161
|
+
const [loading, setLoading] = useState7(true);
|
|
1162
|
+
const [fetchError, setFetchError] = useState7(null);
|
|
896
1163
|
const perArticleEnabled = (_b = (_a = config.perArticleOverride) == null ? void 0 : _a[articleSlug]) != null ? _b : true;
|
|
897
1164
|
const enabled = config.enabled && perArticleEnabled;
|
|
898
1165
|
const currentUserId = (_f = (_e = (_c = session == null ? void 0 : session.user) == null ? void 0 : _c.email) != null ? _e : (_d = session == null ? void 0 : session.user) == null ? void 0 : _d.name) != null ? _f : void 0;
|
|
@@ -918,10 +1185,10 @@ function CommentsSection({ articleSlug, config }) {
|
|
|
918
1185
|
if (!enabled) return null;
|
|
919
1186
|
const count = comments.length;
|
|
920
1187
|
const label = count === 1 ? "1 comment" : `${count} comments`;
|
|
921
|
-
return /* @__PURE__ */
|
|
922
|
-
/* @__PURE__ */
|
|
923
|
-
status === "authenticated" ? /* @__PURE__ */
|
|
924
|
-
/* @__PURE__ */
|
|
1188
|
+
return /* @__PURE__ */ jsxs15("section", { "aria-label": "Comments", className: "mt-12 pt-8 border-t border-border space-y-6", children: [
|
|
1189
|
+
/* @__PURE__ */ jsx17("h2", { className: "text-xl font-semibold text-foreground", children: loading ? "Comments" : label }),
|
|
1190
|
+
status === "authenticated" ? /* @__PURE__ */ jsx17(CommentForm, { articleSlug, onSuccess: fetchComments }) : /* @__PURE__ */ jsxs15("p", { className: "text-sm text-muted-foreground", children: [
|
|
1191
|
+
/* @__PURE__ */ jsx17(
|
|
925
1192
|
"button",
|
|
926
1193
|
{
|
|
927
1194
|
onClick: () => signIn(),
|
|
@@ -932,8 +1199,8 @@ function CommentsSection({ articleSlug, config }) {
|
|
|
932
1199
|
" ",
|
|
933
1200
|
"to join the discussion."
|
|
934
1201
|
] }),
|
|
935
|
-
fetchError && /* @__PURE__ */
|
|
936
|
-
loading ? /* @__PURE__ */
|
|
1202
|
+
fetchError && /* @__PURE__ */ jsx17("p", { className: "text-sm text-destructive", children: fetchError }),
|
|
1203
|
+
loading ? /* @__PURE__ */ jsx17("p", { className: "text-sm text-muted-foreground", children: "Loading comments\u2026" }) : /* @__PURE__ */ jsx17(
|
|
937
1204
|
CommentThread,
|
|
938
1205
|
{
|
|
939
1206
|
comments,
|
|
@@ -947,8 +1214,11 @@ function CommentsSection({ articleSlug, config }) {
|
|
|
947
1214
|
export {
|
|
948
1215
|
ArticleCard,
|
|
949
1216
|
ArticleCategoryGrid,
|
|
1217
|
+
ArticleDetailHero,
|
|
1218
|
+
ArticleNavigation,
|
|
950
1219
|
ArticleSchema,
|
|
951
1220
|
ArticleSearchBar,
|
|
1221
|
+
ArticleSocialShare,
|
|
952
1222
|
ArticlesHero,
|
|
953
1223
|
ArticlesPage,
|
|
954
1224
|
BreadcrumbSchema,
|