@fullstackdatasolutions/articles 0.3.0 → 0.4.3
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 +95 -6
- package/dist/index.cjs +287 -105
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +24 -2
- package/dist/index.d.ts +24 -2
- package/dist/index.js +280 -100
- package/dist/index.js.map +1 -1
- package/dist/server.cjs +63 -3
- package/dist/server.cjs.map +1 -1
- package/dist/server.d.cts +3 -2
- package/dist/server.d.ts +3 -2
- package/dist/server.js +62 -3
- package/dist/server.js.map +1 -1
- package/package.json +3 -2
- package/src/ArticleDetailHero.tsx +31 -5
- package/src/ArticleNavigation.tsx +56 -0
- package/src/ArticleSocialShare.tsx +96 -0
- package/src/CategoryArticlesPage.tsx +21 -4
- package/src/__tests__/ArticleCategoryGrid.test.tsx +34 -1
- package/src/__tests__/ArticleDetailHero.test.tsx +42 -10
- 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 +63 -0
- package/src/index.ts +3 -0
- package/src/seoUtils.ts +66 -2
- package/src/server.ts +1 -0
package/dist/index.cjs
CHANGED
|
@@ -72,8 +72,10 @@ __export(src_exports, {
|
|
|
72
72
|
ArticleCard: () => ArticleCard,
|
|
73
73
|
ArticleCategoryGrid: () => ArticleCategoryGrid,
|
|
74
74
|
ArticleDetailHero: () => ArticleDetailHero,
|
|
75
|
+
ArticleNavigation: () => ArticleNavigation,
|
|
75
76
|
ArticleSchema: () => ArticleSchema,
|
|
76
77
|
ArticleSearchBar: () => ArticleSearchBar,
|
|
78
|
+
ArticleSocialShare: () => ArticleSocialShare,
|
|
77
79
|
ArticlesHero: () => ArticlesHero,
|
|
78
80
|
ArticlesPage: () => ArticlesPage,
|
|
79
81
|
BreadcrumbSchema: () => BreadcrumbSchema,
|
|
@@ -668,7 +670,11 @@ var import_jsx_runtime10 = require("react/jsx-runtime");
|
|
|
668
670
|
function toSlug(cat) {
|
|
669
671
|
return cat.toLowerCase().replaceAll(/\s+/g, "-").replaceAll(/[^a-z0-9-]/g, "");
|
|
670
672
|
}
|
|
671
|
-
function ArticleDetailHero({
|
|
673
|
+
function ArticleDetailHero({
|
|
674
|
+
article,
|
|
675
|
+
categoryBasePath = "/articles/category",
|
|
676
|
+
showDate = false
|
|
677
|
+
}) {
|
|
672
678
|
return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
|
|
673
679
|
"section",
|
|
674
680
|
{
|
|
@@ -689,6 +695,7 @@ function ArticleDetailHero({ article, categoryBasePath }) {
|
|
|
689
695
|
fill: true,
|
|
690
696
|
sizes: "100vw",
|
|
691
697
|
className: "object-cover",
|
|
698
|
+
style: { objectFit: "cover" },
|
|
692
699
|
priority: true
|
|
693
700
|
}
|
|
694
701
|
),
|
|
@@ -716,42 +723,67 @@ function ArticleDetailHero({ article, categoryBasePath }) {
|
|
|
716
723
|
width: "100%"
|
|
717
724
|
},
|
|
718
725
|
children: [
|
|
719
|
-
article.categories && article.categories.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
{
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
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
|
|
726
|
+
article.categories && article.categories.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
|
|
727
|
+
"div",
|
|
728
|
+
{
|
|
729
|
+
style: {
|
|
730
|
+
display: "flex",
|
|
731
|
+
flexWrap: "wrap",
|
|
732
|
+
justifyContent: "center",
|
|
733
|
+
gap: "0.5rem",
|
|
734
|
+
marginBottom: "1rem"
|
|
733
735
|
},
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
736
|
+
children: article.categories.slice(0, 4).map(
|
|
737
|
+
(cat) => categoryBasePath ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
|
|
738
|
+
import_link5.default,
|
|
739
|
+
{
|
|
740
|
+
href: `${categoryBasePath}/${toSlug(cat)}`,
|
|
741
|
+
className: "inline-block bg-white/20 text-white text-sm font-medium px-3 py-1 rounded-full hover:bg-white/30 transition-colors",
|
|
742
|
+
children: cat
|
|
743
|
+
},
|
|
744
|
+
cat
|
|
745
|
+
) : /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
|
|
746
|
+
"span",
|
|
747
|
+
{
|
|
748
|
+
className: "inline-block bg-white/20 text-white text-sm font-medium px-3 py-1 rounded-full",
|
|
749
|
+
children: cat
|
|
750
|
+
},
|
|
751
|
+
cat
|
|
752
|
+
)
|
|
753
|
+
)
|
|
754
|
+
}
|
|
755
|
+
),
|
|
737
756
|
/* @__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)(
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
"
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
757
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
|
|
758
|
+
"div",
|
|
759
|
+
{
|
|
760
|
+
style: {
|
|
761
|
+
display: "flex",
|
|
762
|
+
alignItems: "center",
|
|
763
|
+
justifyContent: "center",
|
|
764
|
+
gap: "1.5rem",
|
|
765
|
+
fontSize: "0.875rem",
|
|
766
|
+
color: "rgba(255,255,255,0.8)"
|
|
767
|
+
},
|
|
768
|
+
children: [
|
|
769
|
+
showDate && article.date && /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: "0.5rem" }, children: [
|
|
770
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_lucide_react5.Calendar, { className: "h-4 w-4" }),
|
|
771
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { children: article.date })
|
|
772
|
+
] }),
|
|
773
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: "0.5rem" }, children: [
|
|
774
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_lucide_react5.Clock, { className: "h-4 w-4" }),
|
|
775
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { children: article.readTime })
|
|
776
|
+
] }),
|
|
777
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: "0.5rem" }, children: [
|
|
778
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_lucide_react5.User, { className: "h-4 w-4" }),
|
|
779
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("span", { children: [
|
|
780
|
+
"By ",
|
|
781
|
+
article.author
|
|
782
|
+
] })
|
|
783
|
+
] })
|
|
784
|
+
]
|
|
785
|
+
}
|
|
786
|
+
)
|
|
755
787
|
]
|
|
756
788
|
}
|
|
757
789
|
)
|
|
@@ -790,30 +822,55 @@ function CategoryArticlesPage({ category, articles, config }) {
|
|
|
790
822
|
]
|
|
791
823
|
}
|
|
792
824
|
),
|
|
793
|
-
/* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
{
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
}
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
825
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
|
|
826
|
+
"section",
|
|
827
|
+
{
|
|
828
|
+
style: {
|
|
829
|
+
position: "relative",
|
|
830
|
+
height: "20rem",
|
|
831
|
+
display: "flex",
|
|
832
|
+
alignItems: "center",
|
|
833
|
+
justifyContent: "center",
|
|
834
|
+
overflow: "hidden"
|
|
835
|
+
},
|
|
836
|
+
children: [
|
|
837
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
|
|
838
|
+
import_image5.default,
|
|
839
|
+
{
|
|
840
|
+
src: heroImage,
|
|
841
|
+
alt: categoryName,
|
|
842
|
+
fill: true,
|
|
843
|
+
sizes: "100vw",
|
|
844
|
+
style: { objectFit: "cover" },
|
|
845
|
+
priority: true
|
|
846
|
+
}
|
|
847
|
+
),
|
|
848
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { style: { position: "absolute", inset: 0, backgroundColor: "rgba(0,0,0,0.6)" } }),
|
|
849
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
|
|
850
|
+
"div",
|
|
851
|
+
{
|
|
852
|
+
style: {
|
|
853
|
+
position: "relative",
|
|
854
|
+
zIndex: 10,
|
|
855
|
+
textAlign: "center",
|
|
856
|
+
color: "white",
|
|
857
|
+
padding: "0 1rem"
|
|
858
|
+
},
|
|
859
|
+
children: [
|
|
860
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)("p", { className: "text-sm font-semibold uppercase tracking-widest mb-3 opacity-80", children: "Category" }),
|
|
861
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)("h1", { className: "text-4xl md:text-5xl font-bold mb-3", children: categoryName }),
|
|
862
|
+
description.long && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("p", { className: "text-lg opacity-90 max-w-2xl mx-auto mt-2", children: description.long }),
|
|
863
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("p", { className: "text-lg opacity-70 mt-2", children: [
|
|
864
|
+
articles.length,
|
|
865
|
+
" article",
|
|
866
|
+
articles.length === 1 ? "" : "s"
|
|
867
|
+
] })
|
|
868
|
+
]
|
|
869
|
+
}
|
|
870
|
+
)
|
|
871
|
+
]
|
|
872
|
+
}
|
|
873
|
+
),
|
|
817
874
|
/* @__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
875
|
/* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "mb-8 flex items-center justify-between", children: [
|
|
819
876
|
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_link6.default, { href: "/articles", className: "text-sm text-primary hover:underline", children: "\u2190 All Articles" }),
|
|
@@ -824,13 +881,136 @@ function CategoryArticlesPage({ category, articles, config }) {
|
|
|
824
881
|
] });
|
|
825
882
|
}
|
|
826
883
|
|
|
884
|
+
// src/ArticleSocialShare.tsx
|
|
885
|
+
var import_react4 = require("react");
|
|
886
|
+
var import_lucide_react6 = require("lucide-react");
|
|
887
|
+
var import_jsx_runtime12 = require("react/jsx-runtime");
|
|
888
|
+
function ArticleSocialShare({ title, url, excerpt, shareMessage }) {
|
|
889
|
+
const [copied, setCopied] = (0, import_react4.useState)(false);
|
|
890
|
+
const encodedTitle = encodeURIComponent(title);
|
|
891
|
+
const encodedUrl = encodeURIComponent(url);
|
|
892
|
+
const encodedExcerpt = encodeURIComponent(excerpt || "");
|
|
893
|
+
const shareLinks = {
|
|
894
|
+
linkedin: `https://www.linkedin.com/sharing/share-offsite/?url=${encodedUrl}`,
|
|
895
|
+
facebook: `https://www.facebook.com/sharer/sharer.php?u=${encodedUrl}`,
|
|
896
|
+
twitter: `https://twitter.com/intent/tweet?text=${encodedTitle}&url=${encodedUrl}`,
|
|
897
|
+
reddit: `https://reddit.com/submit?url=${encodedUrl}&title=${encodedTitle}`,
|
|
898
|
+
email: `mailto:?subject=${encodedTitle}&body=${encodedExcerpt}%0A%0A${encodedUrl}`,
|
|
899
|
+
whatsapp: `https://wa.me/?text=${encodedTitle}%20${encodedUrl}`,
|
|
900
|
+
telegram: `https://t.me/share/url?url=${encodedUrl}&text=${encodedTitle}`
|
|
901
|
+
};
|
|
902
|
+
const copyToClipboard = () => __async(this, null, function* () {
|
|
903
|
+
try {
|
|
904
|
+
yield navigator.clipboard.writeText(url);
|
|
905
|
+
setCopied(true);
|
|
906
|
+
setTimeout(() => setCopied(false), 2e3);
|
|
907
|
+
} catch (e) {
|
|
908
|
+
}
|
|
909
|
+
});
|
|
910
|
+
const openShareWindow = (shareUrl) => {
|
|
911
|
+
window.open(shareUrl, "_blank", "width=600,height=400,scrollbars=yes,resizable=yes");
|
|
912
|
+
};
|
|
913
|
+
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";
|
|
914
|
+
return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "border-t border-border pt-8 mt-8", children: [
|
|
915
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "flex items-center gap-2 mb-4", children: [
|
|
916
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_lucide_react6.Share2, { className: "h-5 w-5 text-muted-foreground" }),
|
|
917
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)("h3", { className: "text-lg font-semibold text-foreground", children: "Share this article" })
|
|
918
|
+
] }),
|
|
919
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "flex flex-wrap gap-2", children: [
|
|
920
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("button", { className: btnClass, onClick: () => openShareWindow(shareLinks.linkedin), children: [
|
|
921
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_lucide_react6.Users, { className: "h-4 w-4" }),
|
|
922
|
+
"LinkedIn"
|
|
923
|
+
] }),
|
|
924
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("button", { className: btnClass, onClick: () => openShareWindow(shareLinks.facebook), children: [
|
|
925
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "text-xs font-bold", children: "f" }),
|
|
926
|
+
" Facebook"
|
|
927
|
+
] }),
|
|
928
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("button", { className: btnClass, onClick: () => openShareWindow(shareLinks.twitter), children: [
|
|
929
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_lucide_react6.MessageCircle, { className: "h-4 w-4" }),
|
|
930
|
+
"Twitter"
|
|
931
|
+
] }),
|
|
932
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("button", { className: btnClass, onClick: () => openShareWindow(shareLinks.reddit), children: [
|
|
933
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_lucide_react6.FileText, { className: "h-4 w-4" }),
|
|
934
|
+
"Reddit"
|
|
935
|
+
] }),
|
|
936
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("button", { className: btnClass, onClick: () => openShareWindow(shareLinks.whatsapp), children: [
|
|
937
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_lucide_react6.MessageCircle, { className: "h-4 w-4" }),
|
|
938
|
+
"WhatsApp"
|
|
939
|
+
] }),
|
|
940
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("button", { className: btnClass, onClick: () => openShareWindow(shareLinks.telegram), children: [
|
|
941
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_lucide_react6.MessageCircle, { className: "h-4 w-4" }),
|
|
942
|
+
"Telegram"
|
|
943
|
+
] }),
|
|
944
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
|
|
945
|
+
"button",
|
|
946
|
+
{
|
|
947
|
+
className: btnClass,
|
|
948
|
+
onClick: () => {
|
|
949
|
+
globalThis.location.href = shareLinks.email;
|
|
950
|
+
},
|
|
951
|
+
children: [
|
|
952
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_lucide_react6.Mail, { className: "h-4 w-4" }),
|
|
953
|
+
"Email"
|
|
954
|
+
]
|
|
955
|
+
}
|
|
956
|
+
),
|
|
957
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("button", { className: btnClass, onClick: copyToClipboard, children: [
|
|
958
|
+
copied ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_lucide_react6.Check, { className: "h-4 w-4" }) : /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_lucide_react6.Copy, { className: "h-4 w-4" }),
|
|
959
|
+
copied ? "Copied!" : "Copy Link"
|
|
960
|
+
] })
|
|
961
|
+
] }),
|
|
962
|
+
shareMessage && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("p", { className: "text-sm text-muted-foreground mt-3", children: shareMessage })
|
|
963
|
+
] });
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
// src/ArticleNavigation.tsx
|
|
967
|
+
var import_link7 = __toESM(require("next/link"), 1);
|
|
968
|
+
var import_lucide_react7 = require("lucide-react");
|
|
969
|
+
var import_jsx_runtime13 = require("react/jsx-runtime");
|
|
970
|
+
function truncateTitle(title, maxLength = 60) {
|
|
971
|
+
return title.length > maxLength ? `${title.substring(0, maxLength)}...` : title;
|
|
972
|
+
}
|
|
973
|
+
function ArticleNavigation({ previous, next, basePath }) {
|
|
974
|
+
if (!previous && !next) return null;
|
|
975
|
+
return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("nav", { className: "mt-12 pt-8 border-t border-border", "aria-label": "Article navigation", children: /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "flex justify-between gap-4", children: [
|
|
976
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "flex-1 min-w-0", children: previous && /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
|
|
977
|
+
import_link7.default,
|
|
978
|
+
{
|
|
979
|
+
href: `${basePath}/${previous.slug}`,
|
|
980
|
+
className: "group flex items-center gap-3 p-4 rounded-lg border border-border hover:border-primary/20 hover:bg-muted/50 transition-colors",
|
|
981
|
+
children: [
|
|
982
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_lucide_react7.ChevronLeft, { className: "h-5 w-5 text-muted-foreground group-hover:text-primary shrink-0" }),
|
|
983
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "min-w-0 flex-1", children: [
|
|
984
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "text-sm text-muted-foreground mb-1", children: "Previous Article" }),
|
|
985
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "font-medium text-foreground truncate", title: previous.title, children: truncateTitle(previous.title) })
|
|
986
|
+
] })
|
|
987
|
+
]
|
|
988
|
+
}
|
|
989
|
+
) }),
|
|
990
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "flex-1 min-w-0", children: next && /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
|
|
991
|
+
import_link7.default,
|
|
992
|
+
{
|
|
993
|
+
href: `${basePath}/${next.slug}`,
|
|
994
|
+
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",
|
|
995
|
+
children: [
|
|
996
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "min-w-0 flex-1 text-right", children: [
|
|
997
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "text-sm text-muted-foreground mb-1", children: "Next Article" }),
|
|
998
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "font-medium text-foreground truncate", title: next.title, children: truncateTitle(next.title) })
|
|
999
|
+
] }),
|
|
1000
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_lucide_react7.ChevronRight, { className: "h-5 w-5 text-muted-foreground group-hover:text-primary shrink-0" })
|
|
1001
|
+
]
|
|
1002
|
+
}
|
|
1003
|
+
) })
|
|
1004
|
+
] }) });
|
|
1005
|
+
}
|
|
1006
|
+
|
|
827
1007
|
// src/CommentsSection.tsx
|
|
828
|
-
var
|
|
829
|
-
var
|
|
1008
|
+
var import_react7 = require("next-auth/react");
|
|
1009
|
+
var import_react8 = require("react");
|
|
830
1010
|
|
|
831
1011
|
// src/CommentForm.tsx
|
|
832
|
-
var
|
|
833
|
-
var
|
|
1012
|
+
var import_react5 = require("react");
|
|
1013
|
+
var import_jsx_runtime14 = require("react/jsx-runtime");
|
|
834
1014
|
var MAX_LENGTH = 2e3;
|
|
835
1015
|
var WARN_THRESHOLD = 1800;
|
|
836
1016
|
function submitLabel(submitting, parentId) {
|
|
@@ -838,9 +1018,9 @@ function submitLabel(submitting, parentId) {
|
|
|
838
1018
|
return parentId ? "Reply" : "Post comment";
|
|
839
1019
|
}
|
|
840
1020
|
function CommentForm({ articleSlug, parentId, onSuccess, onCancel }) {
|
|
841
|
-
const [body, setBody] = (0,
|
|
842
|
-
const [submitting, setSubmitting] = (0,
|
|
843
|
-
const [error, setError] = (0,
|
|
1021
|
+
const [body, setBody] = (0, import_react5.useState)("");
|
|
1022
|
+
const [submitting, setSubmitting] = (0, import_react5.useState)(false);
|
|
1023
|
+
const [error, setError] = (0, import_react5.useState)(null);
|
|
844
1024
|
const remaining = MAX_LENGTH - body.length;
|
|
845
1025
|
function handleSubmit(e) {
|
|
846
1026
|
return __async(this, null, function* () {
|
|
@@ -869,8 +1049,8 @@ function CommentForm({ articleSlug, parentId, onSuccess, onCancel }) {
|
|
|
869
1049
|
}
|
|
870
1050
|
});
|
|
871
1051
|
}
|
|
872
|
-
return /* @__PURE__ */ (0,
|
|
873
|
-
/* @__PURE__ */ (0,
|
|
1052
|
+
return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("form", { onSubmit: handleSubmit, className: "space-y-2", children: [
|
|
1053
|
+
/* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
|
|
874
1054
|
"textarea",
|
|
875
1055
|
{
|
|
876
1056
|
value: body,
|
|
@@ -883,13 +1063,13 @@ function CommentForm({ articleSlug, parentId, onSuccess, onCancel }) {
|
|
|
883
1063
|
"aria-label": parentId ? "Reply text" : "Comment text"
|
|
884
1064
|
}
|
|
885
1065
|
),
|
|
886
|
-
remaining <= MAX_LENGTH - WARN_THRESHOLD && /* @__PURE__ */ (0,
|
|
1066
|
+
remaining <= MAX_LENGTH - WARN_THRESHOLD && /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("p", { className: `text-xs ${remaining < 0 ? "text-destructive" : "text-muted-foreground"}`, children: [
|
|
887
1067
|
remaining,
|
|
888
1068
|
" characters remaining"
|
|
889
1069
|
] }),
|
|
890
|
-
error && /* @__PURE__ */ (0,
|
|
891
|
-
/* @__PURE__ */ (0,
|
|
892
|
-
/* @__PURE__ */ (0,
|
|
1070
|
+
error && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("p", { className: "text-xs text-destructive", children: error }),
|
|
1071
|
+
/* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "flex gap-2", children: [
|
|
1072
|
+
/* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
|
|
893
1073
|
"button",
|
|
894
1074
|
{
|
|
895
1075
|
type: "submit",
|
|
@@ -898,7 +1078,7 @@ function CommentForm({ articleSlug, parentId, onSuccess, onCancel }) {
|
|
|
898
1078
|
children: submitLabel(submitting, parentId)
|
|
899
1079
|
}
|
|
900
1080
|
),
|
|
901
|
-
onCancel && /* @__PURE__ */ (0,
|
|
1081
|
+
onCancel && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
|
|
902
1082
|
"button",
|
|
903
1083
|
{
|
|
904
1084
|
type: "button",
|
|
@@ -912,8 +1092,8 @@ function CommentForm({ articleSlug, parentId, onSuccess, onCancel }) {
|
|
|
912
1092
|
}
|
|
913
1093
|
|
|
914
1094
|
// src/CommentItem.tsx
|
|
915
|
-
var
|
|
916
|
-
var
|
|
1095
|
+
var import_react6 = require("react");
|
|
1096
|
+
var import_jsx_runtime15 = require("react/jsx-runtime");
|
|
917
1097
|
function relativeTime(iso) {
|
|
918
1098
|
const diff = Date.now() - new Date(iso).getTime();
|
|
919
1099
|
const minutes = Math.floor(diff / 6e4);
|
|
@@ -934,8 +1114,8 @@ function CommentItem({
|
|
|
934
1114
|
isReply = false,
|
|
935
1115
|
onRefresh
|
|
936
1116
|
}) {
|
|
937
|
-
const [showReplyForm, setShowReplyForm] = (0,
|
|
938
|
-
const [deleting, setDeleting] = (0,
|
|
1117
|
+
const [showReplyForm, setShowReplyForm] = (0, import_react6.useState)(false);
|
|
1118
|
+
const [deleting, setDeleting] = (0, import_react6.useState)(false);
|
|
939
1119
|
const replies = "replies" in comment ? comment.replies : [];
|
|
940
1120
|
const canReply = !isReply && !!currentUserId && !comment.isDeleted;
|
|
941
1121
|
const canDelete = !!currentUserId && currentUserId === comment.authorId && !comment.isDeleted;
|
|
@@ -951,15 +1131,15 @@ function CommentItem({
|
|
|
951
1131
|
}
|
|
952
1132
|
});
|
|
953
1133
|
}
|
|
954
|
-
return /* @__PURE__ */ (0,
|
|
955
|
-
/* @__PURE__ */ (0,
|
|
956
|
-
/* @__PURE__ */ (0,
|
|
957
|
-
/* @__PURE__ */ (0,
|
|
958
|
-
/* @__PURE__ */ (0,
|
|
1134
|
+
return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: `${isReply ? "ml-8 border-l-2 border-border pl-4" : ""}`, children: [
|
|
1135
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: "rounded-lg bg-muted/40 p-3 space-y-1", children: comment.isDeleted ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("p", { className: "text-sm italic text-muted-foreground", children: "Comment removed" }) : /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(import_jsx_runtime15.Fragment, { children: [
|
|
1136
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "flex items-center justify-between gap-2", children: [
|
|
1137
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "text-sm font-medium text-foreground", children: comment.authorName }),
|
|
1138
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "text-xs text-muted-foreground", children: relativeTime(comment.createdAt) })
|
|
959
1139
|
] }),
|
|
960
|
-
/* @__PURE__ */ (0,
|
|
961
|
-
/* @__PURE__ */ (0,
|
|
962
|
-
canReply && /* @__PURE__ */ (0,
|
|
1140
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)("p", { className: "text-sm text-foreground whitespace-pre-wrap break-words", children: comment.body }),
|
|
1141
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "flex gap-3 pt-1", children: [
|
|
1142
|
+
canReply && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
|
|
963
1143
|
"button",
|
|
964
1144
|
{
|
|
965
1145
|
onClick: () => setShowReplyForm((v) => !v),
|
|
@@ -967,7 +1147,7 @@ function CommentItem({
|
|
|
967
1147
|
children: showReplyForm ? "Cancel" : "Reply"
|
|
968
1148
|
}
|
|
969
1149
|
),
|
|
970
|
-
canDelete && /* @__PURE__ */ (0,
|
|
1150
|
+
canDelete && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
|
|
971
1151
|
"button",
|
|
972
1152
|
{
|
|
973
1153
|
onClick: handleDelete,
|
|
@@ -978,7 +1158,7 @@ function CommentItem({
|
|
|
978
1158
|
)
|
|
979
1159
|
] })
|
|
980
1160
|
] }) }),
|
|
981
|
-
showReplyForm && /* @__PURE__ */ (0,
|
|
1161
|
+
showReplyForm && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: "mt-2 ml-2", children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
|
|
982
1162
|
CommentForm,
|
|
983
1163
|
{
|
|
984
1164
|
articleSlug,
|
|
@@ -990,7 +1170,7 @@ function CommentItem({
|
|
|
990
1170
|
onCancel: () => setShowReplyForm(false)
|
|
991
1171
|
}
|
|
992
1172
|
) }),
|
|
993
|
-
replies.length > 0 && /* @__PURE__ */ (0,
|
|
1173
|
+
replies.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: "mt-2 space-y-2", children: replies.map((reply) => /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
|
|
994
1174
|
CommentItem,
|
|
995
1175
|
{
|
|
996
1176
|
comment: __spreadProps(__spreadValues({}, reply), { replies: [] }),
|
|
@@ -1005,7 +1185,7 @@ function CommentItem({
|
|
|
1005
1185
|
}
|
|
1006
1186
|
|
|
1007
1187
|
// src/CommentThread.tsx
|
|
1008
|
-
var
|
|
1188
|
+
var import_jsx_runtime16 = require("react/jsx-runtime");
|
|
1009
1189
|
function CommentThread({
|
|
1010
1190
|
comments,
|
|
1011
1191
|
articleSlug,
|
|
@@ -1013,9 +1193,9 @@ function CommentThread({
|
|
|
1013
1193
|
onRefresh
|
|
1014
1194
|
}) {
|
|
1015
1195
|
if (comments.length === 0) {
|
|
1016
|
-
return /* @__PURE__ */ (0,
|
|
1196
|
+
return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("p", { className: "text-sm text-muted-foreground text-center py-6", children: "No comments yet. Be the first to share your thoughts." });
|
|
1017
1197
|
}
|
|
1018
|
-
return /* @__PURE__ */ (0,
|
|
1198
|
+
return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { className: "space-y-4", children: comments.map((comment) => /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
1019
1199
|
CommentItem,
|
|
1020
1200
|
{
|
|
1021
1201
|
comment,
|
|
@@ -1028,17 +1208,17 @@ function CommentThread({
|
|
|
1028
1208
|
}
|
|
1029
1209
|
|
|
1030
1210
|
// src/CommentsSection.tsx
|
|
1031
|
-
var
|
|
1211
|
+
var import_jsx_runtime17 = require("react/jsx-runtime");
|
|
1032
1212
|
function CommentsSection({ articleSlug, config }) {
|
|
1033
1213
|
var _a, _b, _c, _d, _e, _f;
|
|
1034
|
-
const { data: session, status } = (0,
|
|
1035
|
-
const [comments, setComments] = (0,
|
|
1036
|
-
const [loading, setLoading] = (0,
|
|
1037
|
-
const [fetchError, setFetchError] = (0,
|
|
1214
|
+
const { data: session, status } = (0, import_react7.useSession)();
|
|
1215
|
+
const [comments, setComments] = (0, import_react8.useState)([]);
|
|
1216
|
+
const [loading, setLoading] = (0, import_react8.useState)(true);
|
|
1217
|
+
const [fetchError, setFetchError] = (0, import_react8.useState)(null);
|
|
1038
1218
|
const perArticleEnabled = (_b = (_a = config.perArticleOverride) == null ? void 0 : _a[articleSlug]) != null ? _b : true;
|
|
1039
1219
|
const enabled = config.enabled && perArticleEnabled;
|
|
1040
1220
|
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;
|
|
1041
|
-
const fetchComments = (0,
|
|
1221
|
+
const fetchComments = (0, import_react8.useCallback)(() => __async(this, null, function* () {
|
|
1042
1222
|
setLoading(true);
|
|
1043
1223
|
setFetchError(null);
|
|
1044
1224
|
try {
|
|
@@ -1052,7 +1232,7 @@ function CommentsSection({ articleSlug, config }) {
|
|
|
1052
1232
|
setLoading(false);
|
|
1053
1233
|
}
|
|
1054
1234
|
}), [articleSlug]);
|
|
1055
|
-
(0,
|
|
1235
|
+
(0, import_react8.useEffect)(() => {
|
|
1056
1236
|
if (enabled) {
|
|
1057
1237
|
fetchComments().catch(() => void 0);
|
|
1058
1238
|
}
|
|
@@ -1060,13 +1240,13 @@ function CommentsSection({ articleSlug, config }) {
|
|
|
1060
1240
|
if (!enabled) return null;
|
|
1061
1241
|
const count = comments.length;
|
|
1062
1242
|
const label = count === 1 ? "1 comment" : `${count} comments`;
|
|
1063
|
-
return /* @__PURE__ */ (0,
|
|
1064
|
-
/* @__PURE__ */ (0,
|
|
1065
|
-
status === "authenticated" ? /* @__PURE__ */ (0,
|
|
1066
|
-
/* @__PURE__ */ (0,
|
|
1243
|
+
return /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("section", { "aria-label": "Comments", className: "mt-12 pt-8 border-t border-border space-y-6", children: [
|
|
1244
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)("h2", { className: "text-xl font-semibold text-foreground", children: loading ? "Comments" : label }),
|
|
1245
|
+
status === "authenticated" ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(CommentForm, { articleSlug, onSuccess: fetchComments }) : /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("p", { className: "text-sm text-muted-foreground", children: [
|
|
1246
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
|
|
1067
1247
|
"button",
|
|
1068
1248
|
{
|
|
1069
|
-
onClick: () => (0,
|
|
1249
|
+
onClick: () => (0, import_react7.signIn)(),
|
|
1070
1250
|
className: "text-primary underline hover:no-underline focus:outline-none",
|
|
1071
1251
|
children: "Sign in"
|
|
1072
1252
|
}
|
|
@@ -1074,8 +1254,8 @@ function CommentsSection({ articleSlug, config }) {
|
|
|
1074
1254
|
" ",
|
|
1075
1255
|
"to join the discussion."
|
|
1076
1256
|
] }),
|
|
1077
|
-
fetchError && /* @__PURE__ */ (0,
|
|
1078
|
-
loading ? /* @__PURE__ */ (0,
|
|
1257
|
+
fetchError && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("p", { className: "text-sm text-destructive", children: fetchError }),
|
|
1258
|
+
loading ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("p", { className: "text-sm text-muted-foreground", children: "Loading comments\u2026" }) : /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
|
|
1079
1259
|
CommentThread,
|
|
1080
1260
|
{
|
|
1081
1261
|
comments,
|
|
@@ -1091,8 +1271,10 @@ function CommentsSection({ articleSlug, config }) {
|
|
|
1091
1271
|
ArticleCard,
|
|
1092
1272
|
ArticleCategoryGrid,
|
|
1093
1273
|
ArticleDetailHero,
|
|
1274
|
+
ArticleNavigation,
|
|
1094
1275
|
ArticleSchema,
|
|
1095
1276
|
ArticleSearchBar,
|
|
1277
|
+
ArticleSocialShare,
|
|
1096
1278
|
ArticlesHero,
|
|
1097
1279
|
ArticlesPage,
|
|
1098
1280
|
BreadcrumbSchema,
|