@fullstackdatasolutions/articles 1.2.3 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/CHANGELOG.md +40 -0
  2. package/README.md +313 -1
  3. package/dist/index.cjs +308 -79
  4. package/dist/index.cjs.map +1 -1
  5. package/dist/index.d.cts +267 -16
  6. package/dist/index.d.ts +267 -16
  7. package/dist/index.js +300 -79
  8. package/dist/index.js.map +1 -1
  9. package/dist/nextjs.cjs +320 -11
  10. package/dist/nextjs.cjs.map +1 -1
  11. package/dist/nextjs.d.cts +179 -2
  12. package/dist/nextjs.d.ts +179 -2
  13. package/dist/nextjs.js +320 -11
  14. package/dist/nextjs.js.map +1 -1
  15. package/dist/server.cjs +655 -47
  16. package/dist/server.cjs.map +1 -1
  17. package/dist/server.d.cts +333 -12
  18. package/dist/server.d.ts +333 -12
  19. package/dist/server.js +640 -47
  20. package/dist/server.js.map +1 -1
  21. package/package.json +1 -1
  22. package/src/ArticleAnswer.tsx +35 -0
  23. package/src/ArticleSchemas.tsx +263 -23
  24. package/src/AuthorArticlesPage.tsx +38 -8
  25. package/src/__tests__/ArticleAnswer.test.tsx +25 -0
  26. package/src/__tests__/ArticleSchemas.test.tsx +516 -0
  27. package/src/__tests__/AuthorArticlesPage.test.tsx +76 -0
  28. package/src/__tests__/authorUtils.test.ts +50 -0
  29. package/src/__tests__/markdown.test.ts +77 -1
  30. package/src/__tests__/nextjs.test.ts +31 -15
  31. package/src/__tests__/seoUtils.test.ts +279 -0
  32. package/src/__tests__/server-articles.test.ts +413 -1
  33. package/src/__tests__/validateArticles.test.ts +167 -6
  34. package/src/articleTypes.ts +57 -0
  35. package/src/articlesConfig.ts +176 -1
  36. package/src/authorUtils.ts +19 -1
  37. package/src/errorReporting.ts +1 -0
  38. package/src/index.ts +17 -1
  39. package/src/markdown.ts +100 -1
  40. package/src/nextjs.ts +7 -4
  41. package/src/seoUtils.ts +247 -26
  42. package/src/server-articles.ts +375 -24
  43. package/src/server.ts +35 -4
  44. package/src/validateArticles.ts +157 -12
package/dist/index.cjs CHANGED
@@ -69,6 +69,7 @@ var __async = (__this, __arguments, generator) => {
69
69
  // src/index.ts
70
70
  var src_exports = {};
71
71
  __export(src_exports, {
72
+ ArticleAnswer: () => ArticleAnswer,
72
73
  ArticleBackLink: () => ArticleBackLink,
73
74
  ArticleCard: () => ArticleCard,
74
75
  ArticleCategoryGrid: () => ArticleCategoryGrid,
@@ -97,16 +98,23 @@ __export(src_exports, {
97
98
  DEFAULT_CATEGORIES_PAGE_SIZE: () => DEFAULT_CATEGORIES_PAGE_SIZE,
98
99
  DEFAULT_LAYOUT: () => DEFAULT_LAYOUT,
99
100
  DEFAULT_PAGE_SIZE: () => DEFAULT_PAGE_SIZE,
101
+ DEFAULT_TITLE_TEMPLATE: () => DEFAULT_TITLE_TEMPLATE,
100
102
  FAQPageSchema: () => FAQPageSchema,
101
103
  FeaturedArticle: () => FeaturedArticle,
102
104
  LatestArticles: () => LatestArticles,
103
105
  LatestArticlesSection: () => LatestArticlesSection,
106
+ OrganizationSchema: () => OrganizationSchema,
104
107
  PaginationNav: () => PaginationNav,
105
108
  RelatedArticlesSection: () => RelatedArticlesSection,
106
109
  ScrollToTop: () => ScrollToTop,
107
110
  SeriesArticlesPage: () => SeriesArticlesPage,
111
+ WebSiteSchema: () => WebSiteSchema,
108
112
  breadcrumbsAreEnabled: () => breadcrumbsAreEnabled,
113
+ formatPageTitle: () => formatPageTitle,
109
114
  getBreadcrumbsConfig: () => getBreadcrumbsConfig,
115
+ getOrganizationId: () => getOrganizationId,
116
+ getPersonId: () => getPersonId,
117
+ getWebSiteId: () => getWebSiteId,
110
118
  useArticles: () => useArticles
111
119
  });
112
120
  module.exports = __toCommonJS(src_exports);
@@ -161,7 +169,51 @@ function ArticleCategoryGrid({ categories, pageSize = 8 }) {
161
169
  ] }) });
162
170
  }
163
171
 
172
+ // src/articlesConfig.ts
173
+ var DEFAULT_PAGE_SIZE = 6;
174
+ var DEFAULT_CATEGORIES_PAGE_SIZE = 8;
175
+ var DEFAULT_LAYOUT = [
176
+ "hero",
177
+ "search",
178
+ "featured",
179
+ "latest",
180
+ "categories"
181
+ ];
182
+ function breadcrumbsAreEnabled(config) {
183
+ var _a;
184
+ return config.breadcrumbs !== false && ((_a = config.breadcrumbs) == null ? void 0 : _a.show) !== false;
185
+ }
186
+ function getBreadcrumbsConfig(config) {
187
+ var _a;
188
+ if (config.breadcrumbs === false) return {};
189
+ return (_a = config.breadcrumbs) != null ? _a : {};
190
+ }
191
+ function getOrganizationId(config) {
192
+ return `${config.siteUrl.replace(/\/$/, "")}/#organization`;
193
+ }
194
+ function getWebSiteId(config) {
195
+ return `${config.siteUrl.replace(/\/$/, "")}/#website`;
196
+ }
197
+ function getPersonId(authorUrl) {
198
+ return `${authorUrl.replace(/\/$/, "")}#person`;
199
+ }
200
+ function resolveEntityUrl(value, config) {
201
+ if (/^https?:\/\//.test(value)) return value;
202
+ return `${config.siteUrl.replace(/\/$/, "")}/${value.replace(/^\/+/, "")}`;
203
+ }
204
+ var DEFAULT_TITLE_TEMPLATE = "{title} | {siteName}";
205
+ function formatPageTitle(title, config) {
206
+ var _a;
207
+ return ((_a = config.titleTemplate) != null ? _a : DEFAULT_TITLE_TEMPLATE).replaceAll("{title}", title).replaceAll("{siteName}", config.siteName);
208
+ }
209
+
164
210
  // src/authorUtils.ts
211
+ function getAuthorIdentityUrl(author, config) {
212
+ if (author.identityUrl) return author.identityUrl;
213
+ if (author.url) return author.url;
214
+ if (!config) return void 0;
215
+ return `${config.siteUrl.replace(/\/$/, "")}/articles/authors/${author.slug}`;
216
+ }
165
217
  function getAuthorUrl(author) {
166
218
  var _a;
167
219
  return (_a = author.url) != null ? _a : `/articles/authors/${author.slug}`;
@@ -176,7 +228,10 @@ function getAuthorAvatar(author, config) {
176
228
  return `${config.siteUrl.replace(/\/$/, "")}${path}`;
177
229
  }
178
230
  function getAuthorSameAs(author) {
179
- return getAuthorSocialLinks(author).map((link) => link.href);
231
+ var _a, _b;
232
+ const derived = getAuthorSocialLinks(author).map((link) => link.href);
233
+ const explicit = (_b = (_a = author.sameAs) == null ? void 0 : _a.filter((url) => url.trim() !== "")) != null ? _b : [];
234
+ return [.../* @__PURE__ */ new Set([...derived, ...explicit])];
180
235
  }
181
236
  function normalizeHandle(value) {
182
237
  return value.replace(/^@/, "");
@@ -309,37 +364,129 @@ function getPersonSchemas(article, authors, config) {
309
364
  } else if (article.author) {
310
365
  resolvedAuthors = [{ name: article.author, slug: "", bio: "" }];
311
366
  }
312
- return resolvedAuthors.map((author) => __spreadValues(__spreadValues(__spreadValues({
313
- "@type": "Person",
314
- name: author.name
315
- }, author.url && { url: author.url }), getAuthorAvatar(author, config) && { image: getAuthorAvatar(author, config) }), getAuthorSameAs(author).length > 0 && { sameAs: getAuthorSameAs(author) }));
367
+ return resolvedAuthors.map((author) => {
368
+ const identityUrl = getAuthorIdentityUrl(author, config);
369
+ return __spreadValues(__spreadValues(__spreadValues(__spreadProps(__spreadValues({
370
+ "@type": "Person"
371
+ }, identityUrl && { "@id": getPersonId(identityUrl) }), {
372
+ name: author.name
373
+ }), author.url && { url: author.url }), getAuthorAvatar(author, config) && { image: getAuthorAvatar(author, config) }), getAuthorSameAs(author).length > 0 && { sameAs: getAuthorSameAs(author) });
374
+ });
316
375
  }
317
- function ArticleSEO({
376
+ function resolveModifiedAt(article, publishedAt, config) {
377
+ if (article.lastmod) return new Date(article.lastmod).toISOString();
378
+ return (config == null ? void 0 : config.lastmodFallback) === "none" ? void 0 : publishedAt;
379
+ }
380
+ function resolveSchemaImageUrl(image, articleUrl, config) {
381
+ if (/^https?:\/\//.test(image)) return image;
382
+ if (config) return resolveEntityUrl(image, config);
383
+ try {
384
+ return `${new URL(articleUrl).origin}/${image.replace(/^\/+/, "")}`;
385
+ } catch (e) {
386
+ return image;
387
+ }
388
+ }
389
+ function buildPublisher(siteName, siteLogo, config) {
390
+ if (config == null ? void 0 : config.organization) return { "@id": getOrganizationId(config) };
391
+ return __spreadValues({
392
+ "@type": "Organization",
393
+ name: siteName
394
+ }, siteLogo && { logo: { "@type": "ImageObject", url: siteLogo } });
395
+ }
396
+ function buildQuestionEntity(article, articleUrl, publishedAt) {
397
+ if (article.articleType !== "QAPage" || !article.answer) return void 0;
398
+ return __spreadProps(__spreadValues({
399
+ "@type": "Question",
400
+ name: article.title,
401
+ text: article.excerpt || article.title
402
+ }, publishedAt && { dateCreated: publishedAt }), {
403
+ answerCount: 1,
404
+ acceptedAnswer: { "@type": "Answer", text: article.answer, url: articleUrl }
405
+ });
406
+ }
407
+ function buildCommentFields(comments) {
408
+ var _a;
409
+ const visible = (_a = comments == null ? void 0 : comments.filter((comment) => !comment.isDeleted && !comment.parentId)) != null ? _a : [];
410
+ if (visible.length === 0) return {};
411
+ return {
412
+ commentCount: visible.length,
413
+ comment: visible.map((comment) => ({
414
+ "@type": "Comment",
415
+ text: comment.body,
416
+ dateCreated: comment.createdAt,
417
+ author: { "@type": "Person", name: comment.authorName }
418
+ }))
419
+ };
420
+ }
421
+ function buildTopicFields(article, config) {
422
+ var _a, _b;
423
+ const about = (_a = article.about) == null ? void 0 : _a.map((entity) => __spreadValues({
424
+ "@type": "Thing",
425
+ name: entity.name
426
+ }, entity.sameAs && { sameAs: entity.sameAs }));
427
+ const citation = (_b = article.citation) == null ? void 0 : _b.map((source) => __spreadValues({
428
+ "@type": "CreativeWork",
429
+ name: source.name
430
+ }, source.url && { url: source.url }));
431
+ const selectors = config == null ? void 0 : config.speakableSelectors;
432
+ return __spreadValues(__spreadValues(__spreadValues({}, (about == null ? void 0 : about.length) && { about }), (citation == null ? void 0 : citation.length) && { citation }), (selectors == null ? void 0 : selectors.length) && {
433
+ speakable: { "@type": "SpeakableSpecification", cssSelector: selectors }
434
+ });
435
+ }
436
+ function buildArticleSchema({
318
437
  article,
319
438
  articleUrl,
320
439
  siteName,
321
440
  siteLogo,
322
- showAuthor = true,
323
- authors,
324
- config
441
+ showAuthor,
442
+ personSchemas,
443
+ config,
444
+ comments
325
445
  }) {
446
+ var _a, _b;
326
447
  const publishedAt = article.date ? new Date(article.date).toISOString() : void 0;
327
- const modifiedAt = article.lastmod ? new Date(article.lastmod).toISOString() : publishedAt;
328
- const personSchemas = getPersonSchemas(article, authors, config);
329
- const structuredData = __spreadValues(__spreadValues(__spreadValues(__spreadProps(__spreadValues(__spreadValues(__spreadValues({
448
+ const modifiedAt = resolveModifiedAt(article, publishedAt, config);
449
+ const mainEntity = buildQuestionEntity(article, articleUrl, publishedAt);
450
+ return __spreadValues(__spreadValues(__spreadValues(__spreadProps(__spreadValues(__spreadValues(__spreadValues(__spreadProps(__spreadValues(__spreadProps(__spreadValues(__spreadValues(__spreadValues({
330
451
  "@context": "https://schema.org",
331
452
  "@type": article.articleType || "Article",
332
453
  mainEntityOfPage: { "@type": "WebPage", "@id": articleUrl },
333
454
  headline: article.title,
334
- image: article.featuredImage ? { "@type": "ImageObject", url: article.featuredImage } : void 0
455
+ image: article.featuredImage ? {
456
+ "@type": "ImageObject",
457
+ url: resolveSchemaImageUrl(article.featuredImage, articleUrl, config)
458
+ } : void 0
335
459
  }, publishedAt && { datePublished: publishedAt }), modifiedAt && { dateModified: modifiedAt }), showAuthor && personSchemas.length > 0 && { author: personSchemas }), {
336
- publisher: __spreadValues({
337
- "@type": "Organization",
338
- name: siteName
339
- }, siteLogo && { logo: { "@type": "ImageObject", url: siteLogo } }),
340
- description: article.excerpt,
460
+ publisher: buildPublisher(siteName, siteLogo, config),
461
+ description: article.excerpt
462
+ }), article.answer && { abstract: article.answer }), {
341
463
  articleSection: article.category
342
- }), article.tags && article.tags.length > 0 && { keywords: article.tags.join(", ") }), article.wordCount !== void 0 && { wordCount: article.wordCount }), article.series && { isPartOf: { "@type": "Blog", name: article.series } });
464
+ }), ((_a = article.tags) == null ? void 0 : _a.length) && { keywords: article.tags.join(", ") }), article.wordCount !== void 0 && { wordCount: article.wordCount }), article.series && { isPartOf: { "@type": "Blog", name: article.series } }), {
465
+ inLanguage: (_b = config == null ? void 0 : config.language) != null ? _b : "en",
466
+ isAccessibleForFree: (config == null ? void 0 : config.isAccessibleForFree) !== false
467
+ }), buildTopicFields(article, config)), buildCommentFields(comments)), mainEntity && { mainEntity });
468
+ }
469
+ function ArticleSEO({
470
+ article,
471
+ articleUrl,
472
+ siteName,
473
+ siteLogo,
474
+ showAuthor = true,
475
+ authors,
476
+ config,
477
+ comments
478
+ }) {
479
+ const personSchemas = getPersonSchemas(article, authors, config);
480
+ const structuredData = buildArticleSchema({
481
+ article,
482
+ articleUrl,
483
+ siteName,
484
+ siteLogo,
485
+ showAuthor,
486
+ personSchemas,
487
+ config,
488
+ comments
489
+ });
343
490
  return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
344
491
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
345
492
  "script",
@@ -436,6 +583,65 @@ function CollectionPageSchema({
436
583
  }
437
584
  );
438
585
  }
586
+ function OrganizationSchema({ config }) {
587
+ var _a, _b, _c, _d, _e;
588
+ const org = config.organization;
589
+ if (!org) return null;
590
+ const schema = __spreadValues(__spreadValues(__spreadValues(__spreadValues({
591
+ "@context": "https://schema.org",
592
+ "@type": (_a = org.type) != null ? _a : "Organization",
593
+ "@id": getOrganizationId(config),
594
+ name: (_b = org.name) != null ? _b : config.siteName,
595
+ url: (_c = org.url) != null ? _c : config.siteUrl.replace(/\/$/, "")
596
+ }, org.logo && {
597
+ logo: { "@type": "ImageObject", url: resolveEntityUrl(org.logo, config) }
598
+ }), ((_d = org.description) != null ? _d : config.description) && {
599
+ description: (_e = org.description) != null ? _e : config.description
600
+ }), org.sameAs && org.sameAs.length > 0 && { sameAs: org.sameAs }), org.parentOrganization && {
601
+ parentOrganization: {
602
+ "@type": "Organization",
603
+ name: org.parentOrganization.name,
604
+ url: org.parentOrganization.url
605
+ }
606
+ });
607
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
608
+ "script",
609
+ {
610
+ type: "application/ld+json",
611
+ dangerouslySetInnerHTML: { __html: JSON.stringify(schema) }
612
+ }
613
+ );
614
+ }
615
+ function WebSiteSchema({ config }) {
616
+ const org = config.organization;
617
+ if (!org) return null;
618
+ const siteUrl = config.siteUrl.replace(/\/$/, "");
619
+ const template = org.searchUrlTemplate;
620
+ const schema = __spreadValues(__spreadValues({
621
+ "@context": "https://schema.org",
622
+ "@type": "WebSite",
623
+ "@id": getWebSiteId(config),
624
+ name: config.siteName,
625
+ url: siteUrl,
626
+ publisher: { "@id": getOrganizationId(config) }
627
+ }, config.description && { description: config.description }), template && {
628
+ potentialAction: {
629
+ "@type": "SearchAction",
630
+ target: {
631
+ "@type": "EntryPoint",
632
+ urlTemplate: resolveEntityUrl(template, config)
633
+ },
634
+ "query-input": "required name=search_term_string"
635
+ }
636
+ });
637
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
638
+ "script",
639
+ {
640
+ type: "application/ld+json",
641
+ dangerouslySetInnerHTML: { __html: JSON.stringify(schema) }
642
+ }
643
+ );
644
+ }
439
645
 
440
646
  // src/ArticleSearchBar.tsx
441
647
  var import_lucide_react = require("lucide-react");
@@ -901,26 +1107,6 @@ function LatestArticlesSection({
901
1107
  ] }) });
902
1108
  }
903
1109
 
904
- // src/articlesConfig.ts
905
- var DEFAULT_PAGE_SIZE = 6;
906
- var DEFAULT_CATEGORIES_PAGE_SIZE = 8;
907
- var DEFAULT_LAYOUT = [
908
- "hero",
909
- "search",
910
- "featured",
911
- "latest",
912
- "categories"
913
- ];
914
- function breadcrumbsAreEnabled(config) {
915
- var _a;
916
- return config.breadcrumbs !== false && ((_a = config.breadcrumbs) == null ? void 0 : _a.show) !== false;
917
- }
918
- function getBreadcrumbsConfig(config) {
919
- var _a;
920
- if (config.breadcrumbs === false) return {};
921
- return (_a = config.breadcrumbs) != null ? _a : {};
922
- }
923
-
924
1110
  // src/useArticles.ts
925
1111
  var import_react3 = require("react");
926
1112
  function useArticles(initialArticles, initialCategories) {
@@ -1623,17 +1809,28 @@ function CtaViewTracker({ ctaId, articleSlug, config, children }) {
1623
1809
 
1624
1810
  // src/AuthorArticlesPage.tsx
1625
1811
  var import_jsx_runtime18 = require("react/jsx-runtime");
1626
- function getPersonSchema(author, config, articleCount) {
1812
+ function getKnowsAbout(author, articles) {
1813
+ var _a;
1814
+ if ((_a = author.knowsAbout) == null ? void 0 : _a.length) return [...author.knowsAbout];
1815
+ return [...new Set(articles.flatMap((article) => {
1816
+ var _a2;
1817
+ return (_a2 = article.categories) != null ? _a2 : [];
1818
+ }))].filter(Boolean);
1819
+ }
1820
+ function getPersonSchema(author, config, articleCount, knowsAbout) {
1627
1821
  var _a, _b;
1628
- return __spreadProps(__spreadValues(__spreadValues({
1822
+ const pageUrl = `${config.siteUrl.replace(/\/$/, "")}/articles/authors/${author.slug}`;
1823
+ const identityUrl = (_a = getAuthorIdentityUrl(author, config)) != null ? _a : pageUrl;
1824
+ const authorUrl = (_b = author.url) != null ? _b : pageUrl;
1825
+ return __spreadProps(__spreadValues(__spreadValues(__spreadValues(__spreadValues({
1629
1826
  "@context": "https://schema.org",
1630
1827
  "@type": "Person",
1828
+ "@id": getPersonId(identityUrl),
1631
1829
  name: author.name,
1632
1830
  description: author.bio,
1633
- url: (_a = author.url) != null ? _a : getAuthorUrl(author)
1634
- }, getAuthorAvatar(author, config) && { image: getAuthorAvatar(author, config) }), getAuthorSameAs(author).length > 0 && { sameAs: getAuthorSameAs(author) }), {
1635
- knowsAbout: config.siteName,
1636
- mainEntityOfPage: (_b = author.url) != null ? _b : `${config.siteUrl.replace(/\/$/, "")}/articles/authors/${author.slug}`,
1831
+ url: authorUrl
1832
+ }, getAuthorAvatar(author, config) && { image: getAuthorAvatar(author, config) }), getAuthorSameAs(author).length > 0 && { sameAs: getAuthorSameAs(author) }), knowsAbout.length > 0 && { knowsAbout: [...knowsAbout] }), config.organization && { worksFor: { "@id": getOrganizationId(config) } }), {
1833
+ mainEntityOfPage: pageUrl,
1637
1834
  interactionStatistic: {
1638
1835
  "@type": "InteractionCounter",
1639
1836
  interactionType: "https://schema.org/WriteAction",
@@ -1786,7 +1983,9 @@ function AuthorArticlesPage({
1786
1983
  {
1787
1984
  type: "application/ld+json",
1788
1985
  dangerouslySetInnerHTML: {
1789
- __html: JSON.stringify(getPersonSchema(author, config, articleCount))
1986
+ __html: JSON.stringify(
1987
+ getPersonSchema(author, config, articleCount, getKnowsAbout(author, articles))
1988
+ )
1790
1989
  }
1791
1990
  }
1792
1991
  ),
@@ -2117,10 +2316,32 @@ function ArticleTOC({ toc, className }) {
2117
2316
  );
2118
2317
  }
2119
2318
 
2319
+ // src/ArticleAnswer.tsx
2320
+ var import_jsx_runtime24 = require("react/jsx-runtime");
2321
+ function ArticleAnswer({
2322
+ article,
2323
+ label = "The short answer",
2324
+ className
2325
+ }) {
2326
+ var _a;
2327
+ if (!((_a = article.answer) == null ? void 0 : _a.trim())) return null;
2328
+ return /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(
2329
+ "aside",
2330
+ {
2331
+ className: `mb-8 rounded-lg border-l-4 border-primary bg-muted/40 px-6 py-4 ${className != null ? className : ""}`,
2332
+ style: { borderLeftWidth: "4px" },
2333
+ children: [
2334
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("p", { className: "mb-2 text-sm font-semibold uppercase tracking-wide text-muted-foreground", children: label }),
2335
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("p", { className: "text-base leading-relaxed", children: article.answer })
2336
+ ]
2337
+ }
2338
+ );
2339
+ }
2340
+
2120
2341
  // src/ScrollToTop.tsx
2121
2342
  var import_react7 = require("react");
2122
2343
  var import_lucide_react11 = require("lucide-react");
2123
- var import_jsx_runtime24 = require("react/jsx-runtime");
2344
+ var import_jsx_runtime25 = require("react/jsx-runtime");
2124
2345
  function ScrollToTop() {
2125
2346
  const [visible, setVisible] = (0, import_react7.useState)(false);
2126
2347
  (0, import_react7.useEffect)(() => {
@@ -2129,13 +2350,13 @@ function ScrollToTop() {
2129
2350
  return () => globalThis.removeEventListener("scroll", onScroll);
2130
2351
  }, []);
2131
2352
  if (!visible) return null;
2132
- return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
2353
+ return /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
2133
2354
  "button",
2134
2355
  {
2135
2356
  "aria-label": "Scroll to top",
2136
2357
  onClick: () => globalThis.scrollTo({ top: 0, behavior: "smooth" }),
2137
2358
  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",
2138
- children: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_lucide_react11.ArrowUp, { className: "h-5 w-5" })
2359
+ children: /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(import_lucide_react11.ArrowUp, { className: "h-5 w-5" })
2139
2360
  }
2140
2361
  );
2141
2362
  }
@@ -2146,7 +2367,7 @@ var import_react11 = require("react");
2146
2367
 
2147
2368
  // src/CommentForm.tsx
2148
2369
  var import_react8 = require("react");
2149
- var import_jsx_runtime25 = require("react/jsx-runtime");
2370
+ var import_jsx_runtime26 = require("react/jsx-runtime");
2150
2371
  var MAX_LENGTH = 2e3;
2151
2372
  var WARN_THRESHOLD = 1800;
2152
2373
  function submitLabel(submitting, parentId) {
@@ -2185,8 +2406,8 @@ function CommentForm({ articleSlug, parentId, onSuccess, onCancel }) {
2185
2406
  }
2186
2407
  });
2187
2408
  }
2188
- return /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("form", { onSubmit: handleSubmit, className: "space-y-2", children: [
2189
- /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
2409
+ return /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)("form", { onSubmit: handleSubmit, className: "space-y-2", children: [
2410
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
2190
2411
  "textarea",
2191
2412
  {
2192
2413
  value: body,
@@ -2199,13 +2420,13 @@ function CommentForm({ articleSlug, parentId, onSuccess, onCancel }) {
2199
2420
  "aria-label": parentId ? "Reply text" : "Comment text"
2200
2421
  }
2201
2422
  ),
2202
- remaining <= MAX_LENGTH - WARN_THRESHOLD && /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("p", { className: `text-xs ${remaining < 0 ? "text-destructive" : "text-muted-foreground"}`, children: [
2423
+ remaining <= MAX_LENGTH - WARN_THRESHOLD && /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)("p", { className: `text-xs ${remaining < 0 ? "text-destructive" : "text-muted-foreground"}`, children: [
2203
2424
  remaining,
2204
2425
  " characters remaining"
2205
2426
  ] }),
2206
- error && /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("p", { className: "text-xs text-destructive", children: error }),
2207
- /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("div", { className: "flex gap-2", children: [
2208
- /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
2427
+ error && /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("p", { className: "text-xs text-destructive", children: error }),
2428
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)("div", { className: "flex gap-2", children: [
2429
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
2209
2430
  "button",
2210
2431
  {
2211
2432
  type: "submit",
@@ -2214,7 +2435,7 @@ function CommentForm({ articleSlug, parentId, onSuccess, onCancel }) {
2214
2435
  children: submitLabel(submitting, parentId)
2215
2436
  }
2216
2437
  ),
2217
- onCancel && /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
2438
+ onCancel && /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
2218
2439
  "button",
2219
2440
  {
2220
2441
  type: "button",
@@ -2229,7 +2450,7 @@ function CommentForm({ articleSlug, parentId, onSuccess, onCancel }) {
2229
2450
 
2230
2451
  // src/CommentItem.tsx
2231
2452
  var import_react9 = require("react");
2232
- var import_jsx_runtime26 = require("react/jsx-runtime");
2453
+ var import_jsx_runtime27 = require("react/jsx-runtime");
2233
2454
  function relativeTime(iso) {
2234
2455
  const diff = Date.now() - new Date(iso).getTime();
2235
2456
  const minutes = Math.floor(diff / 6e4);
@@ -2267,15 +2488,15 @@ function CommentItem({
2267
2488
  }
2268
2489
  });
2269
2490
  }
2270
- return /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)("div", { className: `${isReply ? "ml-8 border-l-2 border-border pl-4" : ""}`, children: [
2271
- /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("div", { className: "rounded-lg bg-muted/40 p-3 space-y-1", children: comment.isDeleted ? /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("p", { className: "text-sm italic text-muted-foreground", children: "Comment removed" }) : /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)(import_jsx_runtime26.Fragment, { children: [
2272
- /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)("div", { className: "flex items-center justify-between gap-2", children: [
2273
- /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("span", { className: "text-sm font-medium text-foreground", children: comment.authorName }),
2274
- /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("span", { className: "text-xs text-muted-foreground", children: relativeTime(comment.createdAt) })
2491
+ return /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("div", { className: `${isReply ? "ml-8 border-l-2 border-border pl-4" : ""}`, children: [
2492
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("div", { className: "rounded-lg bg-muted/40 p-3 space-y-1", children: comment.isDeleted ? /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("p", { className: "text-sm italic text-muted-foreground", children: "Comment removed" }) : /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(import_jsx_runtime27.Fragment, { children: [
2493
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("div", { className: "flex items-center justify-between gap-2", children: [
2494
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("span", { className: "text-sm font-medium text-foreground", children: comment.authorName }),
2495
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("span", { className: "text-xs text-muted-foreground", children: relativeTime(comment.createdAt) })
2275
2496
  ] }),
2276
- /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("p", { className: "text-sm text-foreground whitespace-pre-wrap break-words", children: comment.body }),
2277
- /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)("div", { className: "flex gap-3 pt-1", children: [
2278
- canReply && /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
2497
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("p", { className: "text-sm text-foreground whitespace-pre-wrap break-words", children: comment.body }),
2498
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("div", { className: "flex gap-3 pt-1", children: [
2499
+ canReply && /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
2279
2500
  "button",
2280
2501
  {
2281
2502
  onClick: () => setShowReplyForm((v) => !v),
@@ -2283,7 +2504,7 @@ function CommentItem({
2283
2504
  children: showReplyForm ? "Cancel" : "Reply"
2284
2505
  }
2285
2506
  ),
2286
- canDelete && /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
2507
+ canDelete && /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
2287
2508
  "button",
2288
2509
  {
2289
2510
  onClick: handleDelete,
@@ -2294,7 +2515,7 @@ function CommentItem({
2294
2515
  )
2295
2516
  ] })
2296
2517
  ] }) }),
2297
- showReplyForm && /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("div", { className: "mt-2 ml-2", children: /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
2518
+ showReplyForm && /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("div", { className: "mt-2 ml-2", children: /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
2298
2519
  CommentForm,
2299
2520
  {
2300
2521
  articleSlug,
@@ -2306,7 +2527,7 @@ function CommentItem({
2306
2527
  onCancel: () => setShowReplyForm(false)
2307
2528
  }
2308
2529
  ) }),
2309
- replies.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("div", { className: "mt-2 space-y-2", children: replies.map((reply) => /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
2530
+ replies.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("div", { className: "mt-2 space-y-2", children: replies.map((reply) => /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
2310
2531
  CommentItem,
2311
2532
  {
2312
2533
  comment: __spreadProps(__spreadValues({}, reply), { replies: [] }),
@@ -2321,7 +2542,7 @@ function CommentItem({
2321
2542
  }
2322
2543
 
2323
2544
  // src/CommentThread.tsx
2324
- var import_jsx_runtime27 = require("react/jsx-runtime");
2545
+ var import_jsx_runtime28 = require("react/jsx-runtime");
2325
2546
  function CommentThread({
2326
2547
  comments,
2327
2548
  articleSlug,
@@ -2329,9 +2550,9 @@ function CommentThread({
2329
2550
  onRefresh
2330
2551
  }) {
2331
2552
  if (comments.length === 0) {
2332
- return /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("p", { className: "text-sm text-muted-foreground text-center py-6", children: "No comments yet. Be the first to share your thoughts." });
2553
+ return /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("p", { className: "text-sm text-muted-foreground text-center py-6", children: "No comments yet. Be the first to share your thoughts." });
2333
2554
  }
2334
- return /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("div", { className: "space-y-4", children: comments.map((comment) => /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
2555
+ return /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("div", { className: "space-y-4", children: comments.map((comment) => /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
2335
2556
  CommentItem,
2336
2557
  {
2337
2558
  comment,
@@ -2344,7 +2565,7 @@ function CommentThread({
2344
2565
  }
2345
2566
 
2346
2567
  // src/CommentsSection.tsx
2347
- var import_jsx_runtime28 = require("react/jsx-runtime");
2568
+ var import_jsx_runtime29 = require("react/jsx-runtime");
2348
2569
  function CommentsSection({ articleSlug, config }) {
2349
2570
  var _a, _b, _c, _d, _e, _f;
2350
2571
  const { data: session, status } = (0, import_react10.useSession)();
@@ -2376,10 +2597,10 @@ function CommentsSection({ articleSlug, config }) {
2376
2597
  if (!enabled) return null;
2377
2598
  const count = comments.length;
2378
2599
  const label = count === 1 ? "1 comment" : `${count} comments`;
2379
- return /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("section", { "aria-label": "Comments", className: "mt-12 pt-8 border-t border-border space-y-6", children: [
2380
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("h2", { className: "text-xl font-semibold text-foreground", children: loading ? "Comments" : label }),
2381
- status === "authenticated" ? /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(CommentForm, { articleSlug, onSuccess: fetchComments }) : /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("p", { className: "text-sm text-muted-foreground", children: [
2382
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
2600
+ return /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)("section", { "aria-label": "Comments", className: "mt-12 pt-8 border-t border-border space-y-6", children: [
2601
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("h2", { className: "text-xl font-semibold text-foreground", children: loading ? "Comments" : label }),
2602
+ status === "authenticated" ? /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(CommentForm, { articleSlug, onSuccess: fetchComments }) : /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)("p", { className: "text-sm text-muted-foreground", children: [
2603
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
2383
2604
  "button",
2384
2605
  {
2385
2606
  onClick: () => (0, import_react10.signIn)(),
@@ -2390,8 +2611,8 @@ function CommentsSection({ articleSlug, config }) {
2390
2611
  " ",
2391
2612
  "to join the discussion."
2392
2613
  ] }),
2393
- fetchError && /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("p", { className: "text-sm text-destructive", children: fetchError }),
2394
- loading ? /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("p", { className: "text-sm text-muted-foreground", children: "Loading comments\u2026" }) : /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
2614
+ fetchError && /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("p", { className: "text-sm text-destructive", children: fetchError }),
2615
+ loading ? /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("p", { className: "text-sm text-muted-foreground", children: "Loading comments\u2026" }) : /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
2395
2616
  CommentThread,
2396
2617
  {
2397
2618
  comments,
@@ -2404,6 +2625,7 @@ function CommentsSection({ articleSlug, config }) {
2404
2625
  }
2405
2626
  // Annotate the CommonJS export names for ESM import in node:
2406
2627
  0 && (module.exports = {
2628
+ ArticleAnswer,
2407
2629
  ArticleBackLink,
2408
2630
  ArticleCard,
2409
2631
  ArticleCategoryGrid,
@@ -2432,16 +2654,23 @@ function CommentsSection({ articleSlug, config }) {
2432
2654
  DEFAULT_CATEGORIES_PAGE_SIZE,
2433
2655
  DEFAULT_LAYOUT,
2434
2656
  DEFAULT_PAGE_SIZE,
2657
+ DEFAULT_TITLE_TEMPLATE,
2435
2658
  FAQPageSchema,
2436
2659
  FeaturedArticle,
2437
2660
  LatestArticles,
2438
2661
  LatestArticlesSection,
2662
+ OrganizationSchema,
2439
2663
  PaginationNav,
2440
2664
  RelatedArticlesSection,
2441
2665
  ScrollToTop,
2442
2666
  SeriesArticlesPage,
2667
+ WebSiteSchema,
2443
2668
  breadcrumbsAreEnabled,
2669
+ formatPageTitle,
2444
2670
  getBreadcrumbsConfig,
2671
+ getOrganizationId,
2672
+ getPersonId,
2673
+ getWebSiteId,
2445
2674
  useArticles
2446
2675
  });
2447
2676
  //# sourceMappingURL=index.cjs.map