@fullstackdatasolutions/articles 0.1.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 +324 -0
- package/dist/index.cjs +1027 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +232 -0
- package/dist/index.d.ts +232 -0
- package/dist/index.js +974 -0
- package/dist/index.js.map +1 -0
- package/dist/server.cjs +732 -0
- package/dist/server.cjs.map +1 -0
- package/dist/server.d.cts +127 -0
- package/dist/server.d.ts +127 -0
- package/dist/server.js +684 -0
- package/dist/server.js.map +1 -0
- package/package.json +82 -0
- package/src/ArticleCard.tsx +63 -0
- package/src/ArticleCategoryGrid.tsx +73 -0
- package/src/ArticleSchemas.tsx +82 -0
- package/src/ArticleSearchBar.tsx +50 -0
- package/src/ArticlesHero.tsx +33 -0
- package/src/ArticlesPage.tsx +167 -0
- package/src/CategoryArticlesPage.tsx +84 -0
- package/src/CommentForm.tsx +98 -0
- package/src/CommentItem.tsx +123 -0
- package/src/CommentThread.tsx +40 -0
- package/src/CommentsSection.tsx +84 -0
- package/src/FeaturedArticle.tsx +63 -0
- package/src/LatestArticles.tsx +42 -0
- package/src/LatestArticlesSection.tsx +68 -0
- package/src/__tests__/ArticleCard.test.tsx +78 -0
- package/src/__tests__/ArticleCategoryGrid.test.tsx +98 -0
- package/src/__tests__/ArticleSchemas.test.tsx +130 -0
- package/src/__tests__/ArticleSearchBar.test.tsx +79 -0
- package/src/__tests__/ArticlesHero.test.tsx +38 -0
- package/src/__tests__/ArticlesPage.test.tsx +155 -0
- package/src/__tests__/CategoryArticlesPage.test.tsx +156 -0
- package/src/__tests__/CommentForm.test.tsx +80 -0
- package/src/__tests__/CommentItem.test.tsx +149 -0
- package/src/__tests__/CommentsSection.test.tsx +118 -0
- package/src/__tests__/FeaturedArticle.test.tsx +85 -0
- package/src/__tests__/LatestArticles.test.tsx +157 -0
- package/src/__tests__/LatestArticlesSection.test.tsx +105 -0
- package/src/__tests__/jest-dom.d.ts +1 -0
- package/src/__tests__/seoUtils.test.ts +217 -0
- package/src/__tests__/useArticles.test.ts +207 -0
- package/src/articleTypes.ts +21 -0
- package/src/articlesConfig.ts +98 -0
- package/src/commentTypes.ts +14 -0
- package/src/index.ts +35 -0
- package/src/markdown.ts +314 -0
- package/src/seoUtils.ts +155 -0
- package/src/server-articles.ts +265 -0
- package/src/server.ts +25 -0
- package/src/useArticles.ts +93 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,1027 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
"use strict";
|
|
3
|
+
"use client";
|
|
4
|
+
var __create = Object.create;
|
|
5
|
+
var __defProp = Object.defineProperty;
|
|
6
|
+
var __defProps = Object.defineProperties;
|
|
7
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
8
|
+
var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
|
|
9
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
10
|
+
var __getOwnPropSymbols = Object.getOwnPropertySymbols;
|
|
11
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
12
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
13
|
+
var __propIsEnum = Object.prototype.propertyIsEnumerable;
|
|
14
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
15
|
+
var __spreadValues = (a, b) => {
|
|
16
|
+
for (var prop in b || (b = {}))
|
|
17
|
+
if (__hasOwnProp.call(b, prop))
|
|
18
|
+
__defNormalProp(a, prop, b[prop]);
|
|
19
|
+
if (__getOwnPropSymbols)
|
|
20
|
+
for (var prop of __getOwnPropSymbols(b)) {
|
|
21
|
+
if (__propIsEnum.call(b, prop))
|
|
22
|
+
__defNormalProp(a, prop, b[prop]);
|
|
23
|
+
}
|
|
24
|
+
return a;
|
|
25
|
+
};
|
|
26
|
+
var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
|
|
27
|
+
var __export = (target, all) => {
|
|
28
|
+
for (var name in all)
|
|
29
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
30
|
+
};
|
|
31
|
+
var __copyProps = (to, from, except, desc) => {
|
|
32
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
33
|
+
for (let key of __getOwnPropNames(from))
|
|
34
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
35
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
36
|
+
}
|
|
37
|
+
return to;
|
|
38
|
+
};
|
|
39
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
40
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
41
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
42
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
43
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
44
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
45
|
+
mod
|
|
46
|
+
));
|
|
47
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
48
|
+
var __async = (__this, __arguments, generator) => {
|
|
49
|
+
return new Promise((resolve, reject) => {
|
|
50
|
+
var fulfilled = (value) => {
|
|
51
|
+
try {
|
|
52
|
+
step(generator.next(value));
|
|
53
|
+
} catch (e) {
|
|
54
|
+
reject(e);
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
var rejected = (value) => {
|
|
58
|
+
try {
|
|
59
|
+
step(generator.throw(value));
|
|
60
|
+
} catch (e) {
|
|
61
|
+
reject(e);
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected);
|
|
65
|
+
step((generator = generator.apply(__this, __arguments)).next());
|
|
66
|
+
});
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
// src/index.ts
|
|
70
|
+
var src_exports = {};
|
|
71
|
+
__export(src_exports, {
|
|
72
|
+
ArticleCard: () => ArticleCard,
|
|
73
|
+
ArticleCategoryGrid: () => ArticleCategoryGrid,
|
|
74
|
+
ArticleSchema: () => ArticleSchema,
|
|
75
|
+
ArticleSearchBar: () => ArticleSearchBar,
|
|
76
|
+
ArticlesHero: () => ArticlesHero,
|
|
77
|
+
ArticlesPage: () => ArticlesPage,
|
|
78
|
+
BreadcrumbSchema: () => BreadcrumbSchema,
|
|
79
|
+
CategoryArticlesPage: () => CategoryArticlesPage,
|
|
80
|
+
CollectionPageSchema: () => CollectionPageSchema,
|
|
81
|
+
CommentForm: () => CommentForm,
|
|
82
|
+
CommentItem: () => CommentItem,
|
|
83
|
+
CommentThread: () => CommentThread,
|
|
84
|
+
CommentsSection: () => CommentsSection,
|
|
85
|
+
DEFAULT_CATEGORIES_PAGE_SIZE: () => DEFAULT_CATEGORIES_PAGE_SIZE,
|
|
86
|
+
DEFAULT_LAYOUT: () => DEFAULT_LAYOUT,
|
|
87
|
+
DEFAULT_PAGE_SIZE: () => DEFAULT_PAGE_SIZE,
|
|
88
|
+
FeaturedArticle: () => FeaturedArticle,
|
|
89
|
+
LatestArticles: () => LatestArticles,
|
|
90
|
+
LatestArticlesSection: () => LatestArticlesSection,
|
|
91
|
+
useArticles: () => useArticles
|
|
92
|
+
});
|
|
93
|
+
module.exports = __toCommonJS(src_exports);
|
|
94
|
+
|
|
95
|
+
// src/ArticleCategoryGrid.tsx
|
|
96
|
+
var import_image = __toESM(require("next/image"), 1);
|
|
97
|
+
var import_link = __toESM(require("next/link"), 1);
|
|
98
|
+
var import_react = require("react");
|
|
99
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
100
|
+
function ArticleCategoryGrid({ categories, pageSize = 8 }) {
|
|
101
|
+
const [visibleCount, setVisibleCount] = (0, import_react.useState)(pageSize);
|
|
102
|
+
(0, import_react.useEffect)(() => {
|
|
103
|
+
setVisibleCount(pageSize);
|
|
104
|
+
}, [categories, pageSize]);
|
|
105
|
+
if (categories.length === 0)
|
|
106
|
+
return null;
|
|
107
|
+
const visible = categories.slice(0, visibleCount);
|
|
108
|
+
const hasMore = visibleCount < categories.length;
|
|
109
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("section", { className: "py-16 bg-background", children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "max-w-7xl mx-auto px-4 sm:px-6 lg:px-8", children: [
|
|
110
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "text-center mb-12", children: [
|
|
111
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("h2", { className: "text-3xl font-bold text-foreground mb-4", children: "Browse by Category" }),
|
|
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." })
|
|
113
|
+
] }),
|
|
114
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "grid grid-cols-2 sm:grid-cols-4 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: "relative h-52 overflow-hidden", 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)) }),
|
|
134
|
+
hasMore && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "mt-10 text-center", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
135
|
+
"button",
|
|
136
|
+
{
|
|
137
|
+
type: "button",
|
|
138
|
+
onClick: () => setVisibleCount((c) => c + pageSize),
|
|
139
|
+
className: "inline-flex items-center justify-center px-4 py-2 border border-input bg-background hover:bg-accent rounded-md font-medium text-sm transition-colors",
|
|
140
|
+
children: "Load more categories"
|
|
141
|
+
}
|
|
142
|
+
) })
|
|
143
|
+
] }) });
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// src/ArticleSchemas.tsx
|
|
147
|
+
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
148
|
+
function ArticleSchema({ article, articleUrl, siteName }) {
|
|
149
|
+
const schema = __spreadProps(__spreadValues({
|
|
150
|
+
"@context": "https://schema.org",
|
|
151
|
+
"@type": "Article",
|
|
152
|
+
headline: article.title,
|
|
153
|
+
description: article.excerpt,
|
|
154
|
+
image: article.featuredImage
|
|
155
|
+
}, article.date && { datePublished: new Date(article.date).toISOString() }), {
|
|
156
|
+
author: { "@type": "Person", name: article.author },
|
|
157
|
+
publisher: { "@type": "Organization", name: siteName },
|
|
158
|
+
mainEntityOfPage: { "@type": "WebPage", "@id": articleUrl }
|
|
159
|
+
});
|
|
160
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
161
|
+
"script",
|
|
162
|
+
{
|
|
163
|
+
type: "application/ld+json",
|
|
164
|
+
dangerouslySetInnerHTML: { __html: JSON.stringify(schema) }
|
|
165
|
+
}
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
function BreadcrumbSchema({ items }) {
|
|
169
|
+
const schema = {
|
|
170
|
+
"@context": "https://schema.org",
|
|
171
|
+
"@type": "BreadcrumbList",
|
|
172
|
+
itemListElement: items.map((item, index) => ({
|
|
173
|
+
"@type": "ListItem",
|
|
174
|
+
position: index + 1,
|
|
175
|
+
name: item.name,
|
|
176
|
+
item: item.url
|
|
177
|
+
}))
|
|
178
|
+
};
|
|
179
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
180
|
+
"script",
|
|
181
|
+
{
|
|
182
|
+
type: "application/ld+json",
|
|
183
|
+
dangerouslySetInnerHTML: { __html: JSON.stringify(schema) }
|
|
184
|
+
}
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
function CollectionPageSchema({
|
|
188
|
+
title,
|
|
189
|
+
description,
|
|
190
|
+
url,
|
|
191
|
+
articleCount
|
|
192
|
+
}) {
|
|
193
|
+
const schema = {
|
|
194
|
+
"@context": "https://schema.org",
|
|
195
|
+
"@type": "CollectionPage",
|
|
196
|
+
name: title,
|
|
197
|
+
description,
|
|
198
|
+
url,
|
|
199
|
+
numberOfItems: articleCount
|
|
200
|
+
};
|
|
201
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
202
|
+
"script",
|
|
203
|
+
{
|
|
204
|
+
type: "application/ld+json",
|
|
205
|
+
dangerouslySetInnerHTML: { __html: JSON.stringify(schema) }
|
|
206
|
+
}
|
|
207
|
+
);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// src/ArticleSearchBar.tsx
|
|
211
|
+
var import_lucide_react = require("lucide-react");
|
|
212
|
+
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
213
|
+
function ArticleSearchBar({
|
|
214
|
+
value,
|
|
215
|
+
onChange,
|
|
216
|
+
disabled,
|
|
217
|
+
resultCount
|
|
218
|
+
}) {
|
|
219
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("section", { className: "py-8 bg-background border-b border-border", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "max-w-4xl mx-auto px-4 sm:px-6 lg:px-8", children: [
|
|
220
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "relative", children: [
|
|
221
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_lucide_react.Search, { className: "absolute left-3 top-1/2 transform -translate-y-1/2 text-muted-foreground h-5 w-5" }),
|
|
222
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
223
|
+
"input",
|
|
224
|
+
{
|
|
225
|
+
type: "text",
|
|
226
|
+
placeholder: "Search articles (3+ characters required)...",
|
|
227
|
+
value,
|
|
228
|
+
onChange: (e) => onChange(e.target.value),
|
|
229
|
+
disabled,
|
|
230
|
+
className: "flex w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 pl-10 pr-10 py-3 text-lg"
|
|
231
|
+
}
|
|
232
|
+
),
|
|
233
|
+
value && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
234
|
+
"button",
|
|
235
|
+
{
|
|
236
|
+
type: "button",
|
|
237
|
+
onClick: () => onChange(""),
|
|
238
|
+
"aria-label": "Clear search",
|
|
239
|
+
className: "absolute right-3 top-1/2 transform -translate-y-1/2 text-muted-foreground hover:text-foreground",
|
|
240
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_lucide_react.X, { className: "h-5 w-5" })
|
|
241
|
+
}
|
|
242
|
+
)
|
|
243
|
+
] }),
|
|
244
|
+
value && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "mt-2 text-sm text-muted-foreground", children: resultCount === 0 ? "No articles found matching your search." : `Found ${resultCount} article${resultCount === 1 ? "" : "s"} matching "${value}"` })
|
|
245
|
+
] }) });
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// src/ArticlesHero.tsx
|
|
249
|
+
var import_link2 = __toESM(require("next/link"), 1);
|
|
250
|
+
var import_jsx_runtime4 = require("react/jsx-runtime");
|
|
251
|
+
function ArticlesHero() {
|
|
252
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("section", { className: "bg-linear-to-r from-gray-50 to-gray-100 py-16 md:py-24", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "max-w-7xl mx-auto px-4 sm:px-6 lg:px-8", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "max-w-3xl mx-auto text-center", children: [
|
|
253
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("h1", { className: "text-4xl md:text-5xl font-bold text-foreground mb-6", children: "Vox Populus Insights" }),
|
|
254
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: "text-xl text-muted-foreground mb-8", children: "Expert analysis, campaign strategies, and voter engagement insights from the frontlines of democracy." }),
|
|
255
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "flex flex-col sm:flex-row gap-4 justify-center", children: [
|
|
256
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
257
|
+
import_link2.default,
|
|
258
|
+
{
|
|
259
|
+
href: "#latest",
|
|
260
|
+
className: "inline-flex items-center justify-center px-4 py-2 bg-primary hover:bg-primary/90 text-primary-foreground rounded-md font-medium text-sm transition-colors",
|
|
261
|
+
children: "Browse Latest Articles"
|
|
262
|
+
}
|
|
263
|
+
),
|
|
264
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
265
|
+
import_link2.default,
|
|
266
|
+
{
|
|
267
|
+
href: "/contact",
|
|
268
|
+
className: "inline-flex items-center justify-center px-4 py-2 border border-primary text-primary hover:bg-primary/10 rounded-md font-medium text-sm transition-colors",
|
|
269
|
+
children: "Contact Our Team"
|
|
270
|
+
}
|
|
271
|
+
)
|
|
272
|
+
] })
|
|
273
|
+
] }) }) });
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// src/FeaturedArticle.tsx
|
|
277
|
+
var import_link3 = __toESM(require("next/link"), 1);
|
|
278
|
+
var import_image2 = __toESM(require("next/image"), 1);
|
|
279
|
+
var import_lucide_react2 = require("lucide-react");
|
|
280
|
+
var import_jsx_runtime5 = require("react/jsx-runtime");
|
|
281
|
+
function FeaturedArticle({ article }) {
|
|
282
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("section", { className: "py-16 bg-background", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "max-w-7xl mx-auto px-4 sm:px-6 lg:px-8", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "grid lg:grid-cols-2 gap-12 items-center", children: [
|
|
283
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { children: [
|
|
284
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "inline-block bg-primary/10 text-primary text-sm font-medium px-3 py-1 rounded-full mb-4", children: "FEATURED ARTICLE" }),
|
|
285
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("h2", { className: "text-3xl font-bold text-foreground mb-4", children: article.title }),
|
|
286
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { className: "text-lg text-muted-foreground mb-6", children: article.excerpt }),
|
|
287
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "flex items-center gap-6 text-sm text-muted-foreground mb-6", children: [
|
|
288
|
+
article.date && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "flex items-center gap-2", children: [
|
|
289
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_lucide_react2.Calendar, { className: "h-4 w-4" }),
|
|
290
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { children: article.date })
|
|
291
|
+
] }),
|
|
292
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "flex items-center gap-2", children: [
|
|
293
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_lucide_react2.Clock, { className: "h-4 w-4" }),
|
|
294
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { children: article.readTime })
|
|
295
|
+
] }),
|
|
296
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "flex items-center gap-2", children: [
|
|
297
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_lucide_react2.User, { className: "h-4 w-4" }),
|
|
298
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { children: article.author })
|
|
299
|
+
] })
|
|
300
|
+
] }),
|
|
301
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
|
|
302
|
+
import_link3.default,
|
|
303
|
+
{
|
|
304
|
+
href: `/articles/${article.slug}`,
|
|
305
|
+
className: "inline-flex items-center justify-center px-4 py-2 bg-primary hover:bg-primary/90 text-primary-foreground rounded-md font-medium text-sm transition-colors",
|
|
306
|
+
children: [
|
|
307
|
+
"Read Full Article",
|
|
308
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_lucide_react2.ArrowRight, { className: "ml-2 h-4 w-4" })
|
|
309
|
+
]
|
|
310
|
+
}
|
|
311
|
+
)
|
|
312
|
+
] }),
|
|
313
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "bg-card rounded-lg p-8", children: [
|
|
314
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
315
|
+
import_image2.default,
|
|
316
|
+
{
|
|
317
|
+
src: article.featuredImage,
|
|
318
|
+
alt: article.title,
|
|
319
|
+
width: 800,
|
|
320
|
+
height: 450,
|
|
321
|
+
className: "w-full h-64 object-cover rounded-lg mb-4"
|
|
322
|
+
}
|
|
323
|
+
),
|
|
324
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "flex gap-2", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "bg-primary/10 text-primary text-xs font-medium px-2 py-1 rounded", children: article.category }) })
|
|
325
|
+
] })
|
|
326
|
+
] }) }) });
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// src/LatestArticlesSection.tsx
|
|
330
|
+
var import_lucide_react4 = require("lucide-react");
|
|
331
|
+
|
|
332
|
+
// src/LatestArticles.tsx
|
|
333
|
+
var import_react2 = require("react");
|
|
334
|
+
|
|
335
|
+
// src/ArticleCard.tsx
|
|
336
|
+
var import_image3 = __toESM(require("next/image"), 1);
|
|
337
|
+
var import_link4 = __toESM(require("next/link"), 1);
|
|
338
|
+
var import_lucide_react3 = require("lucide-react");
|
|
339
|
+
var import_jsx_runtime6 = require("react/jsx-runtime");
|
|
340
|
+
function ArticleCard({ article }) {
|
|
341
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "rounded-lg border border-border bg-card text-card-foreground shadow-sm hover:shadow-lg transition-shadow duration-300", children: [
|
|
342
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "p-0", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
343
|
+
import_image3.default,
|
|
344
|
+
{
|
|
345
|
+
src: article.featuredImage,
|
|
346
|
+
alt: article.title,
|
|
347
|
+
className: "w-full h-48 object-cover rounded-t-lg",
|
|
348
|
+
width: 400,
|
|
349
|
+
height: 200
|
|
350
|
+
}
|
|
351
|
+
) }),
|
|
352
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "p-6", children: [
|
|
353
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "flex gap-2 mb-3", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "bg-primary/10 text-primary text-xs font-medium px-2 py-1 rounded", children: article.category }) }),
|
|
354
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("h3", { className: "text-lg font-semibold leading-none tracking-tight mb-2", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
355
|
+
import_link4.default,
|
|
356
|
+
{
|
|
357
|
+
href: `/articles/${article.slug}`,
|
|
358
|
+
className: "hover:text-indigo-600 transition-colors",
|
|
359
|
+
children: article.title
|
|
360
|
+
}
|
|
361
|
+
) }),
|
|
362
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { className: "text-muted-foreground mb-4 line-clamp-3", children: article.excerpt }),
|
|
363
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "flex items-center justify-between text-sm text-muted-foreground border-t pt-4", children: [
|
|
364
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "flex items-center gap-3", children: [
|
|
365
|
+
article.date && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "flex items-center gap-2", children: [
|
|
366
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_lucide_react3.Calendar, { className: "h-3 w-3" }),
|
|
367
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { children: article.date })
|
|
368
|
+
] }),
|
|
369
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "flex items-center gap-2", children: [
|
|
370
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_lucide_react3.Clock, { className: "h-3 w-3" }),
|
|
371
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { children: article.readTime })
|
|
372
|
+
] })
|
|
373
|
+
] }),
|
|
374
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
375
|
+
import_link4.default,
|
|
376
|
+
{
|
|
377
|
+
href: `/articles/${article.slug}`,
|
|
378
|
+
className: "inline-flex items-center justify-center h-8 w-8 p-0 rounded-md hover:bg-accent transition-colors",
|
|
379
|
+
"aria-label": `Read ${article.title}`,
|
|
380
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_lucide_react3.ArrowRight, { className: "h-4 w-4" })
|
|
381
|
+
}
|
|
382
|
+
)
|
|
383
|
+
] })
|
|
384
|
+
] })
|
|
385
|
+
] });
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
// src/LatestArticles.tsx
|
|
389
|
+
var import_jsx_runtime7 = require("react/jsx-runtime");
|
|
390
|
+
function LatestArticles({ articles, pageSize }) {
|
|
391
|
+
const [visibleCount, setVisibleCount] = (0, import_react2.useState)(pageSize);
|
|
392
|
+
(0, import_react2.useEffect)(() => {
|
|
393
|
+
setVisibleCount(pageSize);
|
|
394
|
+
}, [articles, pageSize]);
|
|
395
|
+
const visible = articles.slice(0, visibleCount);
|
|
396
|
+
const hasMore = visibleCount < articles.length;
|
|
397
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { children: [
|
|
398
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "grid md:grid-cols-2 lg:grid-cols-3 gap-8", children: visible.map((article) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(ArticleCard, { article }, article.slug)) }),
|
|
399
|
+
hasMore && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "mt-10 text-center", children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
400
|
+
"button",
|
|
401
|
+
{
|
|
402
|
+
type: "button",
|
|
403
|
+
onClick: () => setVisibleCount((c) => c + pageSize),
|
|
404
|
+
className: "inline-flex items-center justify-center px-4 py-2 border border-input bg-background hover:bg-accent rounded-md font-medium text-sm transition-colors",
|
|
405
|
+
children: "Load more articles"
|
|
406
|
+
}
|
|
407
|
+
) })
|
|
408
|
+
] });
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
// src/LatestArticlesSection.tsx
|
|
412
|
+
var import_jsx_runtime8 = require("react/jsx-runtime");
|
|
413
|
+
function DescriptionText({
|
|
414
|
+
searchQuery,
|
|
415
|
+
count
|
|
416
|
+
}) {
|
|
417
|
+
const pluralSuffix = count === 1 ? "" : "s";
|
|
418
|
+
const text = searchQuery ? `Found ${count} article${pluralSuffix} matching your search.` : "Stay informed with our latest insights on voter engagement, campaign strategies, and civic technology.";
|
|
419
|
+
return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("p", { className: "text-lg text-muted-foreground", children: text });
|
|
420
|
+
}
|
|
421
|
+
function LatestArticlesSection({
|
|
422
|
+
articles,
|
|
423
|
+
searchQuery,
|
|
424
|
+
onClearSearch,
|
|
425
|
+
pageSize = 6
|
|
426
|
+
}) {
|
|
427
|
+
return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("section", { id: "latest", className: "py-16 bg-muted/50", children: /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "max-w-7xl mx-auto px-4 sm:px-6 lg:px-8", children: [
|
|
428
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "text-center mb-12", children: [
|
|
429
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("h2", { className: "text-3xl font-bold text-foreground mb-4", children: searchQuery ? "Search Results" : "Latest Articles" }),
|
|
430
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(DescriptionText, { searchQuery, count: articles.length })
|
|
431
|
+
] }),
|
|
432
|
+
articles.length === 0 && searchQuery ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "text-center py-12", children: /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "max-w-md mx-auto", children: [
|
|
433
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_lucide_react4.Search, { className: "h-12 w-12 text-muted-foreground mx-auto mb-4" }),
|
|
434
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("h3", { className: "text-lg font-semibold text-foreground mb-2", children: "No articles found" }),
|
|
435
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("p", { className: "text-muted-foreground mb-4", children: [
|
|
436
|
+
`We couldn't find any articles matching "`,
|
|
437
|
+
searchQuery,
|
|
438
|
+
'". Try adjusting your search terms.'
|
|
439
|
+
] }),
|
|
440
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
441
|
+
"button",
|
|
442
|
+
{
|
|
443
|
+
type: "button",
|
|
444
|
+
onClick: onClearSearch,
|
|
445
|
+
className: "inline-flex items-center justify-center px-4 py-2 border border-input bg-background hover:bg-accent rounded-md font-medium text-sm transition-colors",
|
|
446
|
+
children: "Clear search"
|
|
447
|
+
}
|
|
448
|
+
)
|
|
449
|
+
] }) }) : /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(LatestArticles, { articles, pageSize }, searchQuery)
|
|
450
|
+
] }) });
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
// src/articlesConfig.ts
|
|
454
|
+
var DEFAULT_PAGE_SIZE = 6;
|
|
455
|
+
var DEFAULT_CATEGORIES_PAGE_SIZE = 8;
|
|
456
|
+
var DEFAULT_LAYOUT = [
|
|
457
|
+
"hero",
|
|
458
|
+
"search",
|
|
459
|
+
"featured",
|
|
460
|
+
"latest",
|
|
461
|
+
"categories"
|
|
462
|
+
];
|
|
463
|
+
|
|
464
|
+
// src/useArticles.ts
|
|
465
|
+
var import_react3 = require("react");
|
|
466
|
+
function useArticles() {
|
|
467
|
+
const [searchQuery, setSearchQuery] = (0, import_react3.useState)("");
|
|
468
|
+
const debounceRef = (0, import_react3.useRef)(null);
|
|
469
|
+
const [articles, setArticles] = (0, import_react3.useState)([]);
|
|
470
|
+
const [categories, setCategories] = (0, import_react3.useState)([]);
|
|
471
|
+
const [loading, setLoading] = (0, import_react3.useState)(true);
|
|
472
|
+
const [error, setError] = (0, import_react3.useState)(null);
|
|
473
|
+
const fetchArticles = (0, import_react3.useCallback)((query) => __async(this, null, function* () {
|
|
474
|
+
try {
|
|
475
|
+
setLoading(true);
|
|
476
|
+
const url = query ? `/api/articles?q=${encodeURIComponent(query)}` : "/api/articles";
|
|
477
|
+
const response = yield fetch(url);
|
|
478
|
+
if (!response.ok)
|
|
479
|
+
throw new Error("Failed to fetch articles");
|
|
480
|
+
const data = yield response.json();
|
|
481
|
+
setArticles(data.articles);
|
|
482
|
+
if (!(query == null ? void 0 : query.trim())) {
|
|
483
|
+
const catMap = /* @__PURE__ */ new Map();
|
|
484
|
+
for (const article of data.articles) {
|
|
485
|
+
for (const cat of article.categories) {
|
|
486
|
+
if (!catMap.has(cat)) {
|
|
487
|
+
catMap.set(cat, { count: 0, featuredImage: article.featuredImage });
|
|
488
|
+
}
|
|
489
|
+
catMap.get(cat).count++;
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
setCategories(
|
|
493
|
+
Array.from(catMap.entries()).map(([name, { count, featuredImage }]) => ({
|
|
494
|
+
name,
|
|
495
|
+
slug: name.toLowerCase().replaceAll(/\s+/g, "-").replaceAll(/[^a-z0-9-]/g, ""),
|
|
496
|
+
count,
|
|
497
|
+
featuredImage
|
|
498
|
+
})).sort((a, b) => b.count - a.count)
|
|
499
|
+
);
|
|
500
|
+
}
|
|
501
|
+
setError(null);
|
|
502
|
+
} catch (err) {
|
|
503
|
+
setError(err instanceof Error ? err.message : "Failed to load articles");
|
|
504
|
+
setArticles([]);
|
|
505
|
+
} finally {
|
|
506
|
+
setLoading(false);
|
|
507
|
+
}
|
|
508
|
+
}), []);
|
|
509
|
+
(0, import_react3.useEffect)(() => {
|
|
510
|
+
fetchArticles("");
|
|
511
|
+
}, [fetchArticles]);
|
|
512
|
+
const handleSearch = (0, import_react3.useCallback)(
|
|
513
|
+
(query) => {
|
|
514
|
+
setSearchQuery(query);
|
|
515
|
+
if (debounceRef.current)
|
|
516
|
+
clearTimeout(debounceRef.current);
|
|
517
|
+
if (query === "") {
|
|
518
|
+
fetchArticles("");
|
|
519
|
+
} else if (query.length >= 3) {
|
|
520
|
+
debounceRef.current = setTimeout(() => fetchArticles(query), 1e3);
|
|
521
|
+
}
|
|
522
|
+
},
|
|
523
|
+
[fetchArticles]
|
|
524
|
+
);
|
|
525
|
+
(0, import_react3.useEffect)(
|
|
526
|
+
() => () => {
|
|
527
|
+
if (debounceRef.current)
|
|
528
|
+
clearTimeout(debounceRef.current);
|
|
529
|
+
},
|
|
530
|
+
[]
|
|
531
|
+
);
|
|
532
|
+
return { articles, categories, searchQuery, loading, error, handleSearch };
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
// src/ArticlesPage.tsx
|
|
536
|
+
var import_jsx_runtime9 = require("react/jsx-runtime");
|
|
537
|
+
function buildThemeVars(theme) {
|
|
538
|
+
if (!theme)
|
|
539
|
+
return {};
|
|
540
|
+
const vars = {};
|
|
541
|
+
if (theme.fontFamily)
|
|
542
|
+
vars["--articles-font-family"] = theme.fontFamily;
|
|
543
|
+
if (theme.headerColor)
|
|
544
|
+
vars["--articles-header-color"] = theme.headerColor;
|
|
545
|
+
if (theme.textColor)
|
|
546
|
+
vars["--articles-text-color"] = theme.textColor;
|
|
547
|
+
if (theme.backgroundColor)
|
|
548
|
+
vars["--articles-bg-color"] = theme.backgroundColor;
|
|
549
|
+
if (theme.linkColor)
|
|
550
|
+
vars["--articles-link-color"] = theme.linkColor;
|
|
551
|
+
if (theme.linkHoverColor)
|
|
552
|
+
vars["--articles-link-hover-color"] = theme.linkHoverColor;
|
|
553
|
+
if (theme.linkTextDecoration)
|
|
554
|
+
vars["--articles-link-decoration"] = theme.linkTextDecoration;
|
|
555
|
+
if (theme.headerFontWeight)
|
|
556
|
+
vars["--articles-header-font-weight"] = String(theme.headerFontWeight);
|
|
557
|
+
if (theme.headerFontSize)
|
|
558
|
+
vars["--articles-header-font-size"] = theme.headerFontSize;
|
|
559
|
+
if (theme.bodyFontSize)
|
|
560
|
+
vars["--articles-body-font-size"] = theme.bodyFontSize;
|
|
561
|
+
if (theme.lineHeight)
|
|
562
|
+
vars["--articles-line-height"] = theme.lineHeight;
|
|
563
|
+
if (theme.borderRadius)
|
|
564
|
+
vars["--articles-border-radius"] = theme.borderRadius;
|
|
565
|
+
return vars;
|
|
566
|
+
}
|
|
567
|
+
function renderSection(section, ctx) {
|
|
568
|
+
const {
|
|
569
|
+
articles,
|
|
570
|
+
displayedArticles,
|
|
571
|
+
categories,
|
|
572
|
+
searchQuery,
|
|
573
|
+
loading,
|
|
574
|
+
error,
|
|
575
|
+
handleSearch,
|
|
576
|
+
pageSize,
|
|
577
|
+
categoriesPageSize
|
|
578
|
+
} = ctx;
|
|
579
|
+
switch (section) {
|
|
580
|
+
case "hero":
|
|
581
|
+
return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(ArticlesHero, {}, "hero");
|
|
582
|
+
case "search":
|
|
583
|
+
return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
|
|
584
|
+
ArticleSearchBar,
|
|
585
|
+
{
|
|
586
|
+
value: searchQuery,
|
|
587
|
+
onChange: handleSearch,
|
|
588
|
+
disabled: loading,
|
|
589
|
+
resultCount: articles.length
|
|
590
|
+
},
|
|
591
|
+
"search"
|
|
592
|
+
);
|
|
593
|
+
case "featured":
|
|
594
|
+
return articles.length > 0 && !searchQuery ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(FeaturedArticle, { article: articles[0] }, "featured") : null;
|
|
595
|
+
case "latest":
|
|
596
|
+
if (loading && articles.length === 0) {
|
|
597
|
+
return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("section", { id: "latest", className: "py-16 bg-muted/50 flex-1", children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 flex items-center justify-center py-24", children: /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "text-center", children: [
|
|
598
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "animate-spin rounded-full h-12 w-12 border-b-2 border-primary mx-auto mb-4" }),
|
|
599
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("p", { className: "text-muted-foreground", children: "Loading articles..." })
|
|
600
|
+
] }) }) }, "latest");
|
|
601
|
+
}
|
|
602
|
+
if (error) {
|
|
603
|
+
return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("section", { id: "latest", className: "py-16 bg-muted/50 flex-1", children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 flex items-center justify-center", children: /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "bg-red-50 border border-red-200 rounded-lg p-6 max-w-md text-center", children: [
|
|
604
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("h3", { className: "text-lg font-semibold text-red-800 mb-2", children: "Error Loading Articles" }),
|
|
605
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("p", { className: "text-red-600 mb-4", children: error }),
|
|
606
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
|
|
607
|
+
"button",
|
|
608
|
+
{
|
|
609
|
+
type: "button",
|
|
610
|
+
onClick: () => handleSearch(""),
|
|
611
|
+
className: "inline-flex items-center justify-center px-4 py-2 border border-input bg-background hover:bg-accent rounded-md font-medium text-sm transition-colors",
|
|
612
|
+
children: "Try again"
|
|
613
|
+
}
|
|
614
|
+
)
|
|
615
|
+
] }) }) }, "latest");
|
|
616
|
+
}
|
|
617
|
+
return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
|
|
618
|
+
LatestArticlesSection,
|
|
619
|
+
{
|
|
620
|
+
articles: displayedArticles,
|
|
621
|
+
searchQuery,
|
|
622
|
+
onClearSearch: () => handleSearch(""),
|
|
623
|
+
pageSize
|
|
624
|
+
},
|
|
625
|
+
"latest"
|
|
626
|
+
);
|
|
627
|
+
case "categories":
|
|
628
|
+
return searchQuery ? null : /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
|
|
629
|
+
ArticleCategoryGrid,
|
|
630
|
+
{
|
|
631
|
+
categories,
|
|
632
|
+
pageSize: categoriesPageSize
|
|
633
|
+
},
|
|
634
|
+
"categories"
|
|
635
|
+
);
|
|
636
|
+
default:
|
|
637
|
+
return null;
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
function ArticlesPage({ config }) {
|
|
641
|
+
var _a, _b, _c;
|
|
642
|
+
const state = useArticles();
|
|
643
|
+
const layout = (_a = config.layout) != null ? _a : DEFAULT_LAYOUT;
|
|
644
|
+
const pageSize = (_b = config.pageSize) != null ? _b : DEFAULT_PAGE_SIZE;
|
|
645
|
+
const categoriesPageSize = (_c = config.categoriesPageSize) != null ? _c : DEFAULT_CATEGORIES_PAGE_SIZE;
|
|
646
|
+
const themeVars = buildThemeVars(config.theme);
|
|
647
|
+
const hasFeatured = layout.includes("featured");
|
|
648
|
+
const articlesWithoutFeatured = hasFeatured ? state.articles.slice(1) : state.articles;
|
|
649
|
+
const displayedArticles = state.searchQuery ? state.articles : articlesWithoutFeatured;
|
|
650
|
+
const ctx = __spreadProps(__spreadValues({}, state), { displayedArticles, pageSize, categoriesPageSize });
|
|
651
|
+
return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { style: themeVars, children: [
|
|
652
|
+
layout.map((section) => renderSection(section, ctx)),
|
|
653
|
+
state.articles.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
|
|
654
|
+
CollectionPageSchema,
|
|
655
|
+
{
|
|
656
|
+
title: `Articles | ${config.siteName}`,
|
|
657
|
+
description: `Browse all ${state.articles.length} articles on ${config.siteName}.`,
|
|
658
|
+
url: `${config.siteUrl}/articles`,
|
|
659
|
+
articleCount: state.articles.length
|
|
660
|
+
}
|
|
661
|
+
)
|
|
662
|
+
] });
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
// src/CategoryArticlesPage.tsx
|
|
666
|
+
var import_image4 = __toESM(require("next/image"), 1);
|
|
667
|
+
var import_link5 = __toESM(require("next/link"), 1);
|
|
668
|
+
var import_jsx_runtime10 = require("react/jsx-runtime");
|
|
669
|
+
function getCategoryDescription(config, slug, categoryName) {
|
|
670
|
+
var _a;
|
|
671
|
+
const raw = (_a = config.categoryDescriptions) == null ? void 0 : _a[slug];
|
|
672
|
+
if (!raw)
|
|
673
|
+
return { short: `Browse all articles in ${categoryName}.` };
|
|
674
|
+
if (typeof raw === "string")
|
|
675
|
+
return { short: raw };
|
|
676
|
+
return raw;
|
|
677
|
+
}
|
|
678
|
+
function CategoryArticlesPage({ category, articles, config }) {
|
|
679
|
+
var _a;
|
|
680
|
+
if (articles.length === 0)
|
|
681
|
+
return null;
|
|
682
|
+
const categoryName = articles[0].category;
|
|
683
|
+
const heroImage = articles[0].featuredImage;
|
|
684
|
+
const description = getCategoryDescription(config, category, categoryName);
|
|
685
|
+
const pageSize = (_a = config.pageSize) != null ? _a : DEFAULT_PAGE_SIZE;
|
|
686
|
+
const siteUrl = config.siteUrl.replace(/\/$/, "");
|
|
687
|
+
return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { children: [
|
|
688
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
|
|
689
|
+
BreadcrumbSchema,
|
|
690
|
+
{
|
|
691
|
+
items: [
|
|
692
|
+
{ name: "Home", url: siteUrl },
|
|
693
|
+
{ name: "Articles", url: `${siteUrl}/articles` },
|
|
694
|
+
{ name: categoryName, url: `${siteUrl}/articles/category/${category}` }
|
|
695
|
+
]
|
|
696
|
+
}
|
|
697
|
+
),
|
|
698
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("section", { className: "relative h-72 md:h-96 flex items-center justify-center overflow-hidden", children: [
|
|
699
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
|
|
700
|
+
import_image4.default,
|
|
701
|
+
{
|
|
702
|
+
src: heroImage,
|
|
703
|
+
alt: categoryName,
|
|
704
|
+
fill: true,
|
|
705
|
+
sizes: "100vw",
|
|
706
|
+
className: "object-cover",
|
|
707
|
+
priority: true
|
|
708
|
+
}
|
|
709
|
+
),
|
|
710
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "absolute inset-0 bg-black/60" }),
|
|
711
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "relative z-10 text-center text-white px-4", children: [
|
|
712
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)("p", { className: "text-sm font-semibold uppercase tracking-widest mb-3 opacity-80", children: "Category" }),
|
|
713
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)("h1", { className: "text-4xl md:text-5xl font-bold mb-3", children: categoryName }),
|
|
714
|
+
description.long && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("p", { className: "text-lg opacity-90 max-w-2xl mx-auto mt-2", children: description.long }),
|
|
715
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("p", { className: "text-lg opacity-70 mt-2", children: [
|
|
716
|
+
articles.length,
|
|
717
|
+
" article",
|
|
718
|
+
articles.length === 1 ? "" : "s"
|
|
719
|
+
] })
|
|
720
|
+
] })
|
|
721
|
+
] }),
|
|
722
|
+
/* @__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: [
|
|
723
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "mb-8 flex items-center justify-between", children: [
|
|
724
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_link5.default, { href: "/articles", className: "text-sm text-primary hover:underline", children: "\u2190 All Articles" }),
|
|
725
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)("p", { className: "text-sm text-muted-foreground", children: description.short })
|
|
726
|
+
] }),
|
|
727
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(LatestArticles, { articles, pageSize })
|
|
728
|
+
] }) })
|
|
729
|
+
] });
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
// src/CommentsSection.tsx
|
|
733
|
+
var import_react6 = require("next-auth/react");
|
|
734
|
+
var import_react7 = require("react");
|
|
735
|
+
|
|
736
|
+
// src/CommentForm.tsx
|
|
737
|
+
var import_react4 = require("react");
|
|
738
|
+
var import_jsx_runtime11 = require("react/jsx-runtime");
|
|
739
|
+
var MAX_LENGTH = 2e3;
|
|
740
|
+
var WARN_THRESHOLD = 1800;
|
|
741
|
+
function submitLabel(submitting, parentId) {
|
|
742
|
+
if (submitting)
|
|
743
|
+
return "Posting\u2026";
|
|
744
|
+
return parentId ? "Reply" : "Post comment";
|
|
745
|
+
}
|
|
746
|
+
function CommentForm({ articleSlug, parentId, onSuccess, onCancel }) {
|
|
747
|
+
const [body, setBody] = (0, import_react4.useState)("");
|
|
748
|
+
const [submitting, setSubmitting] = (0, import_react4.useState)(false);
|
|
749
|
+
const [error, setError] = (0, import_react4.useState)(null);
|
|
750
|
+
const remaining = MAX_LENGTH - body.length;
|
|
751
|
+
function handleSubmit(e) {
|
|
752
|
+
return __async(this, null, function* () {
|
|
753
|
+
var _a;
|
|
754
|
+
e.preventDefault();
|
|
755
|
+
if (!body.trim() || submitting)
|
|
756
|
+
return;
|
|
757
|
+
setSubmitting(true);
|
|
758
|
+
setError(null);
|
|
759
|
+
try {
|
|
760
|
+
const res = yield fetch(`/api/articles/${articleSlug}/comments`, {
|
|
761
|
+
method: "POST",
|
|
762
|
+
headers: { "Content-Type": "application/json" },
|
|
763
|
+
body: JSON.stringify({ body: body.trim(), parentId: parentId != null ? parentId : null })
|
|
764
|
+
});
|
|
765
|
+
if (!res.ok) {
|
|
766
|
+
const data = yield res.json();
|
|
767
|
+
setError((_a = data.error) != null ? _a : "Failed to post comment");
|
|
768
|
+
return;
|
|
769
|
+
}
|
|
770
|
+
setBody("");
|
|
771
|
+
onSuccess();
|
|
772
|
+
} catch (e2) {
|
|
773
|
+
setError("Network error \u2014 please try again");
|
|
774
|
+
} finally {
|
|
775
|
+
setSubmitting(false);
|
|
776
|
+
}
|
|
777
|
+
});
|
|
778
|
+
}
|
|
779
|
+
return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("form", { onSubmit: handleSubmit, className: "space-y-2", children: [
|
|
780
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
|
|
781
|
+
"textarea",
|
|
782
|
+
{
|
|
783
|
+
value: body,
|
|
784
|
+
onChange: (e) => setBody(e.target.value),
|
|
785
|
+
maxLength: MAX_LENGTH,
|
|
786
|
+
rows: parentId ? 3 : 4,
|
|
787
|
+
placeholder: parentId ? "Write a reply\u2026" : "Join the discussion\u2026",
|
|
788
|
+
disabled: submitting,
|
|
789
|
+
className: "w-full rounded-md border border-border bg-background px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-primary/50 disabled:opacity-50 resize-none",
|
|
790
|
+
"aria-label": parentId ? "Reply text" : "Comment text"
|
|
791
|
+
}
|
|
792
|
+
),
|
|
793
|
+
remaining <= MAX_LENGTH - WARN_THRESHOLD && /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("p", { className: `text-xs ${remaining < 0 ? "text-destructive" : "text-muted-foreground"}`, children: [
|
|
794
|
+
remaining,
|
|
795
|
+
" characters remaining"
|
|
796
|
+
] }),
|
|
797
|
+
error && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("p", { className: "text-xs text-destructive", children: error }),
|
|
798
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "flex gap-2", children: [
|
|
799
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
|
|
800
|
+
"button",
|
|
801
|
+
{
|
|
802
|
+
type: "submit",
|
|
803
|
+
disabled: submitting || body.trim().length === 0 || body.length > MAX_LENGTH,
|
|
804
|
+
className: "rounded-md bg-primary px-4 py-1.5 text-sm font-medium text-primary-foreground hover:bg-primary/90 disabled:opacity-50 disabled:cursor-not-allowed",
|
|
805
|
+
children: submitLabel(submitting, parentId)
|
|
806
|
+
}
|
|
807
|
+
),
|
|
808
|
+
onCancel && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
|
|
809
|
+
"button",
|
|
810
|
+
{
|
|
811
|
+
type: "button",
|
|
812
|
+
onClick: onCancel,
|
|
813
|
+
className: "rounded-md px-4 py-1.5 text-sm font-medium text-muted-foreground hover:text-foreground",
|
|
814
|
+
children: "Cancel"
|
|
815
|
+
}
|
|
816
|
+
)
|
|
817
|
+
] })
|
|
818
|
+
] });
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
// src/CommentItem.tsx
|
|
822
|
+
var import_react5 = require("react");
|
|
823
|
+
var import_jsx_runtime12 = require("react/jsx-runtime");
|
|
824
|
+
function relativeTime(iso) {
|
|
825
|
+
const diff = Date.now() - new Date(iso).getTime();
|
|
826
|
+
const minutes = Math.floor(diff / 6e4);
|
|
827
|
+
if (minutes < 1)
|
|
828
|
+
return "just now";
|
|
829
|
+
if (minutes < 60)
|
|
830
|
+
return `${minutes}m ago`;
|
|
831
|
+
const hours = Math.floor(minutes / 60);
|
|
832
|
+
if (hours < 24)
|
|
833
|
+
return `${hours}h ago`;
|
|
834
|
+
const days = Math.floor(hours / 24);
|
|
835
|
+
if (days < 30)
|
|
836
|
+
return `${days}d ago`;
|
|
837
|
+
const months = Math.floor(days / 30);
|
|
838
|
+
if (months < 12)
|
|
839
|
+
return `${months}mo ago`;
|
|
840
|
+
return `${Math.floor(months / 12)}y ago`;
|
|
841
|
+
}
|
|
842
|
+
function CommentItem({
|
|
843
|
+
comment,
|
|
844
|
+
articleSlug,
|
|
845
|
+
currentUserId,
|
|
846
|
+
isReply = false,
|
|
847
|
+
onRefresh
|
|
848
|
+
}) {
|
|
849
|
+
const [showReplyForm, setShowReplyForm] = (0, import_react5.useState)(false);
|
|
850
|
+
const [deleting, setDeleting] = (0, import_react5.useState)(false);
|
|
851
|
+
const replies = "replies" in comment ? comment.replies : [];
|
|
852
|
+
const canReply = !isReply && !!currentUserId && !comment.isDeleted;
|
|
853
|
+
const canDelete = !!currentUserId && currentUserId === comment.authorId && !comment.isDeleted;
|
|
854
|
+
function handleDelete() {
|
|
855
|
+
return __async(this, null, function* () {
|
|
856
|
+
if (deleting)
|
|
857
|
+
return;
|
|
858
|
+
setDeleting(true);
|
|
859
|
+
try {
|
|
860
|
+
yield fetch(`/api/articles/${articleSlug}/comments/${comment.id}`, { method: "DELETE" });
|
|
861
|
+
onRefresh();
|
|
862
|
+
} finally {
|
|
863
|
+
setDeleting(false);
|
|
864
|
+
}
|
|
865
|
+
});
|
|
866
|
+
}
|
|
867
|
+
return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: `${isReply ? "ml-8 border-l-2 border-border pl-4" : ""}`, children: [
|
|
868
|
+
/* @__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: [
|
|
869
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "flex items-center justify-between gap-2", children: [
|
|
870
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "text-sm font-medium text-foreground", children: comment.authorName }),
|
|
871
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "text-xs text-muted-foreground", children: relativeTime(comment.createdAt) })
|
|
872
|
+
] }),
|
|
873
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)("p", { className: "text-sm text-foreground whitespace-pre-wrap break-words", children: comment.body }),
|
|
874
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "flex gap-3 pt-1", children: [
|
|
875
|
+
canReply && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
|
|
876
|
+
"button",
|
|
877
|
+
{
|
|
878
|
+
onClick: () => setShowReplyForm((v) => !v),
|
|
879
|
+
className: "text-xs text-muted-foreground hover:text-foreground",
|
|
880
|
+
children: showReplyForm ? "Cancel" : "Reply"
|
|
881
|
+
}
|
|
882
|
+
),
|
|
883
|
+
canDelete && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
|
|
884
|
+
"button",
|
|
885
|
+
{
|
|
886
|
+
onClick: handleDelete,
|
|
887
|
+
disabled: deleting,
|
|
888
|
+
className: "text-xs text-muted-foreground hover:text-destructive disabled:opacity-50",
|
|
889
|
+
children: deleting ? "Deleting\u2026" : "Delete"
|
|
890
|
+
}
|
|
891
|
+
)
|
|
892
|
+
] })
|
|
893
|
+
] }) }),
|
|
894
|
+
showReplyForm && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "mt-2 ml-2", children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
|
|
895
|
+
CommentForm,
|
|
896
|
+
{
|
|
897
|
+
articleSlug,
|
|
898
|
+
parentId: comment.id,
|
|
899
|
+
onSuccess: () => {
|
|
900
|
+
setShowReplyForm(false);
|
|
901
|
+
onRefresh();
|
|
902
|
+
},
|
|
903
|
+
onCancel: () => setShowReplyForm(false)
|
|
904
|
+
}
|
|
905
|
+
) }),
|
|
906
|
+
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)(
|
|
907
|
+
CommentItem,
|
|
908
|
+
{
|
|
909
|
+
comment: __spreadProps(__spreadValues({}, reply), { replies: [] }),
|
|
910
|
+
articleSlug,
|
|
911
|
+
currentUserId,
|
|
912
|
+
isReply: true,
|
|
913
|
+
onRefresh
|
|
914
|
+
},
|
|
915
|
+
reply.id
|
|
916
|
+
)) })
|
|
917
|
+
] });
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
// src/CommentThread.tsx
|
|
921
|
+
var import_jsx_runtime13 = require("react/jsx-runtime");
|
|
922
|
+
function CommentThread({
|
|
923
|
+
comments,
|
|
924
|
+
articleSlug,
|
|
925
|
+
currentUserId,
|
|
926
|
+
onRefresh
|
|
927
|
+
}) {
|
|
928
|
+
if (comments.length === 0) {
|
|
929
|
+
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." });
|
|
930
|
+
}
|
|
931
|
+
return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "space-y-4", children: comments.map((comment) => /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
|
|
932
|
+
CommentItem,
|
|
933
|
+
{
|
|
934
|
+
comment,
|
|
935
|
+
articleSlug,
|
|
936
|
+
currentUserId,
|
|
937
|
+
onRefresh
|
|
938
|
+
},
|
|
939
|
+
comment.id
|
|
940
|
+
)) });
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
// src/CommentsSection.tsx
|
|
944
|
+
var import_jsx_runtime14 = require("react/jsx-runtime");
|
|
945
|
+
function CommentsSection({ articleSlug, config }) {
|
|
946
|
+
var _a, _b, _c, _d, _e, _f;
|
|
947
|
+
const { data: session, status } = (0, import_react6.useSession)();
|
|
948
|
+
const [comments, setComments] = (0, import_react7.useState)([]);
|
|
949
|
+
const [loading, setLoading] = (0, import_react7.useState)(true);
|
|
950
|
+
const [fetchError, setFetchError] = (0, import_react7.useState)(null);
|
|
951
|
+
const perArticleEnabled = (_b = (_a = config.perArticleOverride) == null ? void 0 : _a[articleSlug]) != null ? _b : true;
|
|
952
|
+
const enabled = config.enabled && perArticleEnabled;
|
|
953
|
+
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;
|
|
954
|
+
const fetchComments = (0, import_react7.useCallback)(() => __async(this, null, function* () {
|
|
955
|
+
setLoading(true);
|
|
956
|
+
setFetchError(null);
|
|
957
|
+
try {
|
|
958
|
+
const res = yield fetch(`/api/articles/${articleSlug}/comments`);
|
|
959
|
+
if (!res.ok)
|
|
960
|
+
throw new Error("Failed to load comments");
|
|
961
|
+
const data = yield res.json();
|
|
962
|
+
setComments(data.comments);
|
|
963
|
+
} catch (e) {
|
|
964
|
+
setFetchError("Could not load comments. Please try again.");
|
|
965
|
+
} finally {
|
|
966
|
+
setLoading(false);
|
|
967
|
+
}
|
|
968
|
+
}), [articleSlug]);
|
|
969
|
+
(0, import_react7.useEffect)(() => {
|
|
970
|
+
if (enabled) {
|
|
971
|
+
fetchComments().catch(() => void 0);
|
|
972
|
+
}
|
|
973
|
+
}, [fetchComments, enabled]);
|
|
974
|
+
if (!enabled)
|
|
975
|
+
return null;
|
|
976
|
+
const count = comments.length;
|
|
977
|
+
const label = count === 1 ? "1 comment" : `${count} comments`;
|
|
978
|
+
return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("section", { "aria-label": "Comments", className: "mt-12 pt-8 border-t border-border space-y-6", children: [
|
|
979
|
+
/* @__PURE__ */ (0, import_jsx_runtime14.jsx)("h2", { className: "text-xl font-semibold text-foreground", children: loading ? "Comments" : label }),
|
|
980
|
+
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: [
|
|
981
|
+
/* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
|
|
982
|
+
"button",
|
|
983
|
+
{
|
|
984
|
+
onClick: () => (0, import_react6.signIn)(),
|
|
985
|
+
className: "text-primary underline hover:no-underline focus:outline-none",
|
|
986
|
+
children: "Sign in"
|
|
987
|
+
}
|
|
988
|
+
),
|
|
989
|
+
" ",
|
|
990
|
+
"to join the discussion."
|
|
991
|
+
] }),
|
|
992
|
+
fetchError && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("p", { className: "text-sm text-destructive", children: fetchError }),
|
|
993
|
+
loading ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("p", { className: "text-sm text-muted-foreground", children: "Loading comments\u2026" }) : /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
|
|
994
|
+
CommentThread,
|
|
995
|
+
{
|
|
996
|
+
comments,
|
|
997
|
+
articleSlug,
|
|
998
|
+
currentUserId,
|
|
999
|
+
onRefresh: fetchComments
|
|
1000
|
+
}
|
|
1001
|
+
)
|
|
1002
|
+
] });
|
|
1003
|
+
}
|
|
1004
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
1005
|
+
0 && (module.exports = {
|
|
1006
|
+
ArticleCard,
|
|
1007
|
+
ArticleCategoryGrid,
|
|
1008
|
+
ArticleSchema,
|
|
1009
|
+
ArticleSearchBar,
|
|
1010
|
+
ArticlesHero,
|
|
1011
|
+
ArticlesPage,
|
|
1012
|
+
BreadcrumbSchema,
|
|
1013
|
+
CategoryArticlesPage,
|
|
1014
|
+
CollectionPageSchema,
|
|
1015
|
+
CommentForm,
|
|
1016
|
+
CommentItem,
|
|
1017
|
+
CommentThread,
|
|
1018
|
+
CommentsSection,
|
|
1019
|
+
DEFAULT_CATEGORIES_PAGE_SIZE,
|
|
1020
|
+
DEFAULT_LAYOUT,
|
|
1021
|
+
DEFAULT_PAGE_SIZE,
|
|
1022
|
+
FeaturedArticle,
|
|
1023
|
+
LatestArticles,
|
|
1024
|
+
LatestArticlesSection,
|
|
1025
|
+
useArticles
|
|
1026
|
+
});
|
|
1027
|
+
//# sourceMappingURL=index.cjs.map
|