@fullstackdatasolutions/articles 0.4.3 → 0.5.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/README.md +208 -19
- package/dist/index.cjs +182 -47
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +50 -1
- package/dist/index.d.ts +50 -1
- package/dist/index.js +241 -110
- package/dist/index.js.map +1 -1
- package/dist/server.cjs +247 -188
- package/dist/server.cjs.map +1 -1
- package/dist/server.d.cts +42 -1
- package/dist/server.d.ts +42 -1
- package/dist/server.js +244 -188
- package/dist/server.js.map +1 -1
- package/package.json +5 -1
- package/src/ArticleContent.tsx +17 -0
- package/src/ArticleSchemas.tsx +97 -1
- package/src/ArticleSocialShare.tsx +1 -1
- package/src/ArticleTOC.tsx +29 -0
- package/src/ScrollToTop.tsx +25 -0
- package/src/__tests__/ArticleContent.test.tsx +91 -0
- package/src/__tests__/ArticleSchemas.test.tsx +212 -1
- package/src/__tests__/ArticleSocialShare.test.tsx +7 -7
- package/src/__tests__/ArticleTOC.test.tsx +75 -0
- package/src/__tests__/ScrollToTop.test.tsx +87 -0
- package/src/__tests__/seoUtils.test.ts +266 -3
- package/src/__tests__/server-articles.test.ts +98 -0
- package/src/articleTypes.ts +26 -0
- package/src/articlesConfig.ts +4 -0
- package/src/index.ts +10 -2
- package/src/markdown.ts +143 -130
- package/src/seoUtils.ts +20 -14
- package/src/server-articles.ts +81 -78
- package/src/server.ts +4 -2
package/dist/index.cjs
CHANGED
|
@@ -73,9 +73,11 @@ __export(src_exports, {
|
|
|
73
73
|
ArticleCategoryGrid: () => ArticleCategoryGrid,
|
|
74
74
|
ArticleDetailHero: () => ArticleDetailHero,
|
|
75
75
|
ArticleNavigation: () => ArticleNavigation,
|
|
76
|
+
ArticleSEO: () => ArticleSEO,
|
|
76
77
|
ArticleSchema: () => ArticleSchema,
|
|
77
78
|
ArticleSearchBar: () => ArticleSearchBar,
|
|
78
79
|
ArticleSocialShare: () => ArticleSocialShare,
|
|
80
|
+
ArticleTOC: () => ArticleTOC,
|
|
79
81
|
ArticlesHero: () => ArticlesHero,
|
|
80
82
|
ArticlesPage: () => ArticlesPage,
|
|
81
83
|
BreadcrumbSchema: () => BreadcrumbSchema,
|
|
@@ -88,9 +90,11 @@ __export(src_exports, {
|
|
|
88
90
|
DEFAULT_CATEGORIES_PAGE_SIZE: () => DEFAULT_CATEGORIES_PAGE_SIZE,
|
|
89
91
|
DEFAULT_LAYOUT: () => DEFAULT_LAYOUT,
|
|
90
92
|
DEFAULT_PAGE_SIZE: () => DEFAULT_PAGE_SIZE,
|
|
93
|
+
FAQPageSchema: () => FAQPageSchema,
|
|
91
94
|
FeaturedArticle: () => FeaturedArticle,
|
|
92
95
|
LatestArticles: () => LatestArticles,
|
|
93
96
|
LatestArticlesSection: () => LatestArticlesSection,
|
|
97
|
+
ScrollToTop: () => ScrollToTop,
|
|
94
98
|
useArticles: () => useArticles
|
|
95
99
|
});
|
|
96
100
|
module.exports = __toCommonJS(src_exports);
|
|
@@ -186,6 +190,86 @@ function BreadcrumbSchema({ items }) {
|
|
|
186
190
|
}
|
|
187
191
|
);
|
|
188
192
|
}
|
|
193
|
+
function ArticleSEO({ article, articleUrl, siteName, siteLogo }) {
|
|
194
|
+
const publishedAt = article.date ? new Date(article.date).toISOString() : void 0;
|
|
195
|
+
const modifiedAt = article.lastmod ? new Date(article.lastmod).toISOString() : publishedAt;
|
|
196
|
+
const structuredData = __spreadValues(__spreadProps(__spreadValues(__spreadValues({
|
|
197
|
+
"@context": "https://schema.org",
|
|
198
|
+
"@type": article.articleType || "Article",
|
|
199
|
+
mainEntityOfPage: { "@type": "WebPage", "@id": articleUrl },
|
|
200
|
+
headline: article.title,
|
|
201
|
+
image: article.featuredImage ? { "@type": "ImageObject", url: article.featuredImage } : void 0
|
|
202
|
+
}, publishedAt && { datePublished: publishedAt }), modifiedAt && { dateModified: modifiedAt }), {
|
|
203
|
+
author: { "@type": "Person", name: article.author },
|
|
204
|
+
publisher: __spreadValues({
|
|
205
|
+
"@type": "Organization",
|
|
206
|
+
name: siteName
|
|
207
|
+
}, siteLogo && { logo: { "@type": "ImageObject", url: siteLogo } }),
|
|
208
|
+
description: article.excerpt
|
|
209
|
+
}), article.series && { isPartOf: { "@type": "Blog", name: article.series } });
|
|
210
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
|
|
211
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
212
|
+
"script",
|
|
213
|
+
{
|
|
214
|
+
type: "application/ld+json",
|
|
215
|
+
dangerouslySetInnerHTML: { __html: JSON.stringify(structuredData) }
|
|
216
|
+
}
|
|
217
|
+
),
|
|
218
|
+
article.faq && article.faq.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
219
|
+
"script",
|
|
220
|
+
{
|
|
221
|
+
type: "application/ld+json",
|
|
222
|
+
dangerouslySetInnerHTML: {
|
|
223
|
+
__html: JSON.stringify({
|
|
224
|
+
"@context": "https://schema.org",
|
|
225
|
+
"@type": "FAQPage",
|
|
226
|
+
mainEntity: article.faq.map(({ question, answer }) => ({
|
|
227
|
+
"@type": "Question",
|
|
228
|
+
name: question,
|
|
229
|
+
acceptedAnswer: { "@type": "Answer", text: answer }
|
|
230
|
+
}))
|
|
231
|
+
})
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
),
|
|
235
|
+
article.howTo && article.howTo.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
236
|
+
"script",
|
|
237
|
+
{
|
|
238
|
+
type: "application/ld+json",
|
|
239
|
+
dangerouslySetInnerHTML: {
|
|
240
|
+
__html: JSON.stringify({
|
|
241
|
+
"@context": "https://schema.org",
|
|
242
|
+
"@type": "HowTo",
|
|
243
|
+
name: article.title,
|
|
244
|
+
step: article.howTo.map(({ name, text }) => ({
|
|
245
|
+
"@type": "HowToStep",
|
|
246
|
+
name,
|
|
247
|
+
text
|
|
248
|
+
}))
|
|
249
|
+
})
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
)
|
|
253
|
+
] });
|
|
254
|
+
}
|
|
255
|
+
function FAQPageSchema({ items }) {
|
|
256
|
+
const schema = {
|
|
257
|
+
"@context": "https://schema.org",
|
|
258
|
+
"@type": "FAQPage",
|
|
259
|
+
mainEntity: items.map(({ question, answer }) => ({
|
|
260
|
+
"@type": "Question",
|
|
261
|
+
name: question,
|
|
262
|
+
acceptedAnswer: { "@type": "Answer", text: answer }
|
|
263
|
+
}))
|
|
264
|
+
};
|
|
265
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
266
|
+
"script",
|
|
267
|
+
{
|
|
268
|
+
type: "application/ld+json",
|
|
269
|
+
dangerouslySetInnerHTML: { __html: JSON.stringify(schema) }
|
|
270
|
+
}
|
|
271
|
+
);
|
|
272
|
+
}
|
|
189
273
|
function CollectionPageSchema({
|
|
190
274
|
title,
|
|
191
275
|
description,
|
|
@@ -908,7 +992,7 @@ function ArticleSocialShare({ title, url, excerpt, shareMessage }) {
|
|
|
908
992
|
}
|
|
909
993
|
});
|
|
910
994
|
const openShareWindow = (shareUrl) => {
|
|
911
|
-
|
|
995
|
+
globalThis.open(shareUrl, "_blank", "width=600,height=400,scrollbars=yes,resizable=yes");
|
|
912
996
|
};
|
|
913
997
|
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
998
|
return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "border-t border-border pt-8 mt-8", children: [
|
|
@@ -1004,13 +1088,60 @@ function ArticleNavigation({ previous, next, basePath }) {
|
|
|
1004
1088
|
] }) });
|
|
1005
1089
|
}
|
|
1006
1090
|
|
|
1091
|
+
// src/ArticleTOC.tsx
|
|
1092
|
+
var import_jsx_runtime14 = require("react/jsx-runtime");
|
|
1093
|
+
function ArticleTOC({ toc, className }) {
|
|
1094
|
+
if (!toc.length) return null;
|
|
1095
|
+
return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(
|
|
1096
|
+
"nav",
|
|
1097
|
+
{
|
|
1098
|
+
"aria-label": "Table of contents",
|
|
1099
|
+
className: `mb-8 rounded-lg border border-border bg-muted/40 px-6 py-4 ${className != null ? className : ""}`,
|
|
1100
|
+
children: [
|
|
1101
|
+
/* @__PURE__ */ (0, import_jsx_runtime14.jsx)("p", { className: "mb-3 text-sm font-semibold uppercase tracking-wide text-muted-foreground", children: "On this page" }),
|
|
1102
|
+
/* @__PURE__ */ (0, import_jsx_runtime14.jsx)("ul", { className: "space-y-1 text-sm", children: toc.map((item) => /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("li", { style: { paddingLeft: `${Math.max(0, item.depth - 2) * 1}rem` }, children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
|
|
1103
|
+
"a",
|
|
1104
|
+
{
|
|
1105
|
+
href: `#${item.id}`,
|
|
1106
|
+
className: "text-muted-foreground hover:text-foreground transition-colors",
|
|
1107
|
+
children: item.text
|
|
1108
|
+
}
|
|
1109
|
+
) }, item.id)) })
|
|
1110
|
+
]
|
|
1111
|
+
}
|
|
1112
|
+
);
|
|
1113
|
+
}
|
|
1114
|
+
|
|
1115
|
+
// src/ScrollToTop.tsx
|
|
1116
|
+
var import_react5 = require("react");
|
|
1117
|
+
var import_lucide_react8 = require("lucide-react");
|
|
1118
|
+
var import_jsx_runtime15 = require("react/jsx-runtime");
|
|
1119
|
+
function ScrollToTop() {
|
|
1120
|
+
const [visible, setVisible] = (0, import_react5.useState)(false);
|
|
1121
|
+
(0, import_react5.useEffect)(() => {
|
|
1122
|
+
const onScroll = () => setVisible(globalThis.scrollY > 300);
|
|
1123
|
+
globalThis.addEventListener("scroll", onScroll, { passive: true });
|
|
1124
|
+
return () => globalThis.removeEventListener("scroll", onScroll);
|
|
1125
|
+
}, []);
|
|
1126
|
+
if (!visible) return null;
|
|
1127
|
+
return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
|
|
1128
|
+
"button",
|
|
1129
|
+
{
|
|
1130
|
+
"aria-label": "Scroll to top",
|
|
1131
|
+
onClick: () => globalThis.scrollTo({ top: 0, behavior: "smooth" }),
|
|
1132
|
+
className: "fixed bottom-8 right-8 z-50 rounded-full bg-primary p-2 shadow-md text-primary-foreground hover:bg-primary/90 transition-colors",
|
|
1133
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_lucide_react8.ArrowUp, { className: "h-5 w-5" })
|
|
1134
|
+
}
|
|
1135
|
+
);
|
|
1136
|
+
}
|
|
1137
|
+
|
|
1007
1138
|
// src/CommentsSection.tsx
|
|
1008
|
-
var
|
|
1009
|
-
var
|
|
1139
|
+
var import_react8 = require("next-auth/react");
|
|
1140
|
+
var import_react9 = require("react");
|
|
1010
1141
|
|
|
1011
1142
|
// src/CommentForm.tsx
|
|
1012
|
-
var
|
|
1013
|
-
var
|
|
1143
|
+
var import_react6 = require("react");
|
|
1144
|
+
var import_jsx_runtime16 = require("react/jsx-runtime");
|
|
1014
1145
|
var MAX_LENGTH = 2e3;
|
|
1015
1146
|
var WARN_THRESHOLD = 1800;
|
|
1016
1147
|
function submitLabel(submitting, parentId) {
|
|
@@ -1018,9 +1149,9 @@ function submitLabel(submitting, parentId) {
|
|
|
1018
1149
|
return parentId ? "Reply" : "Post comment";
|
|
1019
1150
|
}
|
|
1020
1151
|
function CommentForm({ articleSlug, parentId, onSuccess, onCancel }) {
|
|
1021
|
-
const [body, setBody] = (0,
|
|
1022
|
-
const [submitting, setSubmitting] = (0,
|
|
1023
|
-
const [error, setError] = (0,
|
|
1152
|
+
const [body, setBody] = (0, import_react6.useState)("");
|
|
1153
|
+
const [submitting, setSubmitting] = (0, import_react6.useState)(false);
|
|
1154
|
+
const [error, setError] = (0, import_react6.useState)(null);
|
|
1024
1155
|
const remaining = MAX_LENGTH - body.length;
|
|
1025
1156
|
function handleSubmit(e) {
|
|
1026
1157
|
return __async(this, null, function* () {
|
|
@@ -1049,8 +1180,8 @@ function CommentForm({ articleSlug, parentId, onSuccess, onCancel }) {
|
|
|
1049
1180
|
}
|
|
1050
1181
|
});
|
|
1051
1182
|
}
|
|
1052
|
-
return /* @__PURE__ */ (0,
|
|
1053
|
-
/* @__PURE__ */ (0,
|
|
1183
|
+
return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("form", { onSubmit: handleSubmit, className: "space-y-2", children: [
|
|
1184
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
1054
1185
|
"textarea",
|
|
1055
1186
|
{
|
|
1056
1187
|
value: body,
|
|
@@ -1063,13 +1194,13 @@ function CommentForm({ articleSlug, parentId, onSuccess, onCancel }) {
|
|
|
1063
1194
|
"aria-label": parentId ? "Reply text" : "Comment text"
|
|
1064
1195
|
}
|
|
1065
1196
|
),
|
|
1066
|
-
remaining <= MAX_LENGTH - WARN_THRESHOLD && /* @__PURE__ */ (0,
|
|
1197
|
+
remaining <= MAX_LENGTH - WARN_THRESHOLD && /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("p", { className: `text-xs ${remaining < 0 ? "text-destructive" : "text-muted-foreground"}`, children: [
|
|
1067
1198
|
remaining,
|
|
1068
1199
|
" characters remaining"
|
|
1069
1200
|
] }),
|
|
1070
|
-
error && /* @__PURE__ */ (0,
|
|
1071
|
-
/* @__PURE__ */ (0,
|
|
1072
|
-
/* @__PURE__ */ (0,
|
|
1201
|
+
error && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("p", { className: "text-xs text-destructive", children: error }),
|
|
1202
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: "flex gap-2", children: [
|
|
1203
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
1073
1204
|
"button",
|
|
1074
1205
|
{
|
|
1075
1206
|
type: "submit",
|
|
@@ -1078,7 +1209,7 @@ function CommentForm({ articleSlug, parentId, onSuccess, onCancel }) {
|
|
|
1078
1209
|
children: submitLabel(submitting, parentId)
|
|
1079
1210
|
}
|
|
1080
1211
|
),
|
|
1081
|
-
onCancel && /* @__PURE__ */ (0,
|
|
1212
|
+
onCancel && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
|
|
1082
1213
|
"button",
|
|
1083
1214
|
{
|
|
1084
1215
|
type: "button",
|
|
@@ -1092,8 +1223,8 @@ function CommentForm({ articleSlug, parentId, onSuccess, onCancel }) {
|
|
|
1092
1223
|
}
|
|
1093
1224
|
|
|
1094
1225
|
// src/CommentItem.tsx
|
|
1095
|
-
var
|
|
1096
|
-
var
|
|
1226
|
+
var import_react7 = require("react");
|
|
1227
|
+
var import_jsx_runtime17 = require("react/jsx-runtime");
|
|
1097
1228
|
function relativeTime(iso) {
|
|
1098
1229
|
const diff = Date.now() - new Date(iso).getTime();
|
|
1099
1230
|
const minutes = Math.floor(diff / 6e4);
|
|
@@ -1114,8 +1245,8 @@ function CommentItem({
|
|
|
1114
1245
|
isReply = false,
|
|
1115
1246
|
onRefresh
|
|
1116
1247
|
}) {
|
|
1117
|
-
const [showReplyForm, setShowReplyForm] = (0,
|
|
1118
|
-
const [deleting, setDeleting] = (0,
|
|
1248
|
+
const [showReplyForm, setShowReplyForm] = (0, import_react7.useState)(false);
|
|
1249
|
+
const [deleting, setDeleting] = (0, import_react7.useState)(false);
|
|
1119
1250
|
const replies = "replies" in comment ? comment.replies : [];
|
|
1120
1251
|
const canReply = !isReply && !!currentUserId && !comment.isDeleted;
|
|
1121
1252
|
const canDelete = !!currentUserId && currentUserId === comment.authorId && !comment.isDeleted;
|
|
@@ -1131,15 +1262,15 @@ function CommentItem({
|
|
|
1131
1262
|
}
|
|
1132
1263
|
});
|
|
1133
1264
|
}
|
|
1134
|
-
return /* @__PURE__ */ (0,
|
|
1135
|
-
/* @__PURE__ */ (0,
|
|
1136
|
-
/* @__PURE__ */ (0,
|
|
1137
|
-
/* @__PURE__ */ (0,
|
|
1138
|
-
/* @__PURE__ */ (0,
|
|
1265
|
+
return /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: `${isReply ? "ml-8 border-l-2 border-border pl-4" : ""}`, children: [
|
|
1266
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { className: "rounded-lg bg-muted/40 p-3 space-y-1", children: comment.isDeleted ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("p", { className: "text-sm italic text-muted-foreground", children: "Comment removed" }) : /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(import_jsx_runtime17.Fragment, { children: [
|
|
1267
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: "flex items-center justify-between gap-2", children: [
|
|
1268
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { className: "text-sm font-medium text-foreground", children: comment.authorName }),
|
|
1269
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { className: "text-xs text-muted-foreground", children: relativeTime(comment.createdAt) })
|
|
1139
1270
|
] }),
|
|
1140
|
-
/* @__PURE__ */ (0,
|
|
1141
|
-
/* @__PURE__ */ (0,
|
|
1142
|
-
canReply && /* @__PURE__ */ (0,
|
|
1271
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)("p", { className: "text-sm text-foreground whitespace-pre-wrap break-words", children: comment.body }),
|
|
1272
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: "flex gap-3 pt-1", children: [
|
|
1273
|
+
canReply && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
|
|
1143
1274
|
"button",
|
|
1144
1275
|
{
|
|
1145
1276
|
onClick: () => setShowReplyForm((v) => !v),
|
|
@@ -1147,7 +1278,7 @@ function CommentItem({
|
|
|
1147
1278
|
children: showReplyForm ? "Cancel" : "Reply"
|
|
1148
1279
|
}
|
|
1149
1280
|
),
|
|
1150
|
-
canDelete && /* @__PURE__ */ (0,
|
|
1281
|
+
canDelete && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
|
|
1151
1282
|
"button",
|
|
1152
1283
|
{
|
|
1153
1284
|
onClick: handleDelete,
|
|
@@ -1158,7 +1289,7 @@ function CommentItem({
|
|
|
1158
1289
|
)
|
|
1159
1290
|
] })
|
|
1160
1291
|
] }) }),
|
|
1161
|
-
showReplyForm && /* @__PURE__ */ (0,
|
|
1292
|
+
showReplyForm && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { className: "mt-2 ml-2", children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
|
|
1162
1293
|
CommentForm,
|
|
1163
1294
|
{
|
|
1164
1295
|
articleSlug,
|
|
@@ -1170,7 +1301,7 @@ function CommentItem({
|
|
|
1170
1301
|
onCancel: () => setShowReplyForm(false)
|
|
1171
1302
|
}
|
|
1172
1303
|
) }),
|
|
1173
|
-
replies.length > 0 && /* @__PURE__ */ (0,
|
|
1304
|
+
replies.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { className: "mt-2 space-y-2", children: replies.map((reply) => /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
|
|
1174
1305
|
CommentItem,
|
|
1175
1306
|
{
|
|
1176
1307
|
comment: __spreadProps(__spreadValues({}, reply), { replies: [] }),
|
|
@@ -1185,7 +1316,7 @@ function CommentItem({
|
|
|
1185
1316
|
}
|
|
1186
1317
|
|
|
1187
1318
|
// src/CommentThread.tsx
|
|
1188
|
-
var
|
|
1319
|
+
var import_jsx_runtime18 = require("react/jsx-runtime");
|
|
1189
1320
|
function CommentThread({
|
|
1190
1321
|
comments,
|
|
1191
1322
|
articleSlug,
|
|
@@ -1193,9 +1324,9 @@ function CommentThread({
|
|
|
1193
1324
|
onRefresh
|
|
1194
1325
|
}) {
|
|
1195
1326
|
if (comments.length === 0) {
|
|
1196
|
-
return /* @__PURE__ */ (0,
|
|
1327
|
+
return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("p", { className: "text-sm text-muted-foreground text-center py-6", children: "No comments yet. Be the first to share your thoughts." });
|
|
1197
1328
|
}
|
|
1198
|
-
return /* @__PURE__ */ (0,
|
|
1329
|
+
return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "space-y-4", children: comments.map((comment) => /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
|
|
1199
1330
|
CommentItem,
|
|
1200
1331
|
{
|
|
1201
1332
|
comment,
|
|
@@ -1208,17 +1339,17 @@ function CommentThread({
|
|
|
1208
1339
|
}
|
|
1209
1340
|
|
|
1210
1341
|
// src/CommentsSection.tsx
|
|
1211
|
-
var
|
|
1342
|
+
var import_jsx_runtime19 = require("react/jsx-runtime");
|
|
1212
1343
|
function CommentsSection({ articleSlug, config }) {
|
|
1213
1344
|
var _a, _b, _c, _d, _e, _f;
|
|
1214
|
-
const { data: session, status } = (0,
|
|
1215
|
-
const [comments, setComments] = (0,
|
|
1216
|
-
const [loading, setLoading] = (0,
|
|
1217
|
-
const [fetchError, setFetchError] = (0,
|
|
1345
|
+
const { data: session, status } = (0, import_react8.useSession)();
|
|
1346
|
+
const [comments, setComments] = (0, import_react9.useState)([]);
|
|
1347
|
+
const [loading, setLoading] = (0, import_react9.useState)(true);
|
|
1348
|
+
const [fetchError, setFetchError] = (0, import_react9.useState)(null);
|
|
1218
1349
|
const perArticleEnabled = (_b = (_a = config.perArticleOverride) == null ? void 0 : _a[articleSlug]) != null ? _b : true;
|
|
1219
1350
|
const enabled = config.enabled && perArticleEnabled;
|
|
1220
1351
|
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;
|
|
1221
|
-
const fetchComments = (0,
|
|
1352
|
+
const fetchComments = (0, import_react9.useCallback)(() => __async(this, null, function* () {
|
|
1222
1353
|
setLoading(true);
|
|
1223
1354
|
setFetchError(null);
|
|
1224
1355
|
try {
|
|
@@ -1232,7 +1363,7 @@ function CommentsSection({ articleSlug, config }) {
|
|
|
1232
1363
|
setLoading(false);
|
|
1233
1364
|
}
|
|
1234
1365
|
}), [articleSlug]);
|
|
1235
|
-
(0,
|
|
1366
|
+
(0, import_react9.useEffect)(() => {
|
|
1236
1367
|
if (enabled) {
|
|
1237
1368
|
fetchComments().catch(() => void 0);
|
|
1238
1369
|
}
|
|
@@ -1240,13 +1371,13 @@ function CommentsSection({ articleSlug, config }) {
|
|
|
1240
1371
|
if (!enabled) return null;
|
|
1241
1372
|
const count = comments.length;
|
|
1242
1373
|
const label = count === 1 ? "1 comment" : `${count} comments`;
|
|
1243
|
-
return /* @__PURE__ */ (0,
|
|
1244
|
-
/* @__PURE__ */ (0,
|
|
1245
|
-
status === "authenticated" ? /* @__PURE__ */ (0,
|
|
1246
|
-
/* @__PURE__ */ (0,
|
|
1374
|
+
return /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("section", { "aria-label": "Comments", className: "mt-12 pt-8 border-t border-border space-y-6", children: [
|
|
1375
|
+
/* @__PURE__ */ (0, import_jsx_runtime19.jsx)("h2", { className: "text-xl font-semibold text-foreground", children: loading ? "Comments" : label }),
|
|
1376
|
+
status === "authenticated" ? /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(CommentForm, { articleSlug, onSuccess: fetchComments }) : /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("p", { className: "text-sm text-muted-foreground", children: [
|
|
1377
|
+
/* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
|
|
1247
1378
|
"button",
|
|
1248
1379
|
{
|
|
1249
|
-
onClick: () => (0,
|
|
1380
|
+
onClick: () => (0, import_react8.signIn)(),
|
|
1250
1381
|
className: "text-primary underline hover:no-underline focus:outline-none",
|
|
1251
1382
|
children: "Sign in"
|
|
1252
1383
|
}
|
|
@@ -1254,8 +1385,8 @@ function CommentsSection({ articleSlug, config }) {
|
|
|
1254
1385
|
" ",
|
|
1255
1386
|
"to join the discussion."
|
|
1256
1387
|
] }),
|
|
1257
|
-
fetchError && /* @__PURE__ */ (0,
|
|
1258
|
-
loading ? /* @__PURE__ */ (0,
|
|
1388
|
+
fetchError && /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("p", { className: "text-sm text-destructive", children: fetchError }),
|
|
1389
|
+
loading ? /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("p", { className: "text-sm text-muted-foreground", children: "Loading comments\u2026" }) : /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
|
|
1259
1390
|
CommentThread,
|
|
1260
1391
|
{
|
|
1261
1392
|
comments,
|
|
@@ -1272,9 +1403,11 @@ function CommentsSection({ articleSlug, config }) {
|
|
|
1272
1403
|
ArticleCategoryGrid,
|
|
1273
1404
|
ArticleDetailHero,
|
|
1274
1405
|
ArticleNavigation,
|
|
1406
|
+
ArticleSEO,
|
|
1275
1407
|
ArticleSchema,
|
|
1276
1408
|
ArticleSearchBar,
|
|
1277
1409
|
ArticleSocialShare,
|
|
1410
|
+
ArticleTOC,
|
|
1278
1411
|
ArticlesHero,
|
|
1279
1412
|
ArticlesPage,
|
|
1280
1413
|
BreadcrumbSchema,
|
|
@@ -1287,9 +1420,11 @@ function CommentsSection({ articleSlug, config }) {
|
|
|
1287
1420
|
DEFAULT_CATEGORIES_PAGE_SIZE,
|
|
1288
1421
|
DEFAULT_LAYOUT,
|
|
1289
1422
|
DEFAULT_PAGE_SIZE,
|
|
1423
|
+
FAQPageSchema,
|
|
1290
1424
|
FeaturedArticle,
|
|
1291
1425
|
LatestArticles,
|
|
1292
1426
|
LatestArticlesSection,
|
|
1427
|
+
ScrollToTop,
|
|
1293
1428
|
useArticles
|
|
1294
1429
|
});
|
|
1295
1430
|
//# sourceMappingURL=index.cjs.map
|