@fullstackdatasolutions/articles 1.2.3 → 1.3.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.
Files changed (44) hide show
  1. package/CHANGELOG.md +46 -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 +325 -31
  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 +325 -31
  14. package/dist/nextjs.js.map +1 -1
  15. package/dist/server.cjs +660 -50
  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 +645 -50
  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 +434 -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 +385 -25
  43. package/src/server.ts +35 -4
  44. package/src/validateArticles.ts +157 -12
package/dist/index.js CHANGED
@@ -90,7 +90,51 @@ function ArticleCategoryGrid({ categories, pageSize = 8 }) {
90
90
  ] }) });
91
91
  }
92
92
 
93
+ // src/articlesConfig.ts
94
+ var DEFAULT_PAGE_SIZE = 6;
95
+ var DEFAULT_CATEGORIES_PAGE_SIZE = 8;
96
+ var DEFAULT_LAYOUT = [
97
+ "hero",
98
+ "search",
99
+ "featured",
100
+ "latest",
101
+ "categories"
102
+ ];
103
+ function breadcrumbsAreEnabled(config) {
104
+ var _a;
105
+ return config.breadcrumbs !== false && ((_a = config.breadcrumbs) == null ? void 0 : _a.show) !== false;
106
+ }
107
+ function getBreadcrumbsConfig(config) {
108
+ var _a;
109
+ if (config.breadcrumbs === false) return {};
110
+ return (_a = config.breadcrumbs) != null ? _a : {};
111
+ }
112
+ function getOrganizationId(config) {
113
+ return `${config.siteUrl.replace(/\/$/, "")}/#organization`;
114
+ }
115
+ function getWebSiteId(config) {
116
+ return `${config.siteUrl.replace(/\/$/, "")}/#website`;
117
+ }
118
+ function getPersonId(authorUrl) {
119
+ return `${authorUrl.replace(/\/$/, "")}#person`;
120
+ }
121
+ function resolveEntityUrl(value, config) {
122
+ if (/^https?:\/\//.test(value)) return value;
123
+ return `${config.siteUrl.replace(/\/$/, "")}/${value.replace(/^\/+/, "")}`;
124
+ }
125
+ var DEFAULT_TITLE_TEMPLATE = "{title} | {siteName}";
126
+ function formatPageTitle(title, config) {
127
+ var _a;
128
+ return ((_a = config.titleTemplate) != null ? _a : DEFAULT_TITLE_TEMPLATE).replaceAll("{title}", title).replaceAll("{siteName}", config.siteName);
129
+ }
130
+
93
131
  // src/authorUtils.ts
132
+ function getAuthorIdentityUrl(author, config) {
133
+ if (author.identityUrl) return author.identityUrl;
134
+ if (author.url) return author.url;
135
+ if (!config) return void 0;
136
+ return `${config.siteUrl.replace(/\/$/, "")}/articles/authors/${author.slug}`;
137
+ }
94
138
  function getAuthorUrl(author) {
95
139
  var _a;
96
140
  return (_a = author.url) != null ? _a : `/articles/authors/${author.slug}`;
@@ -105,7 +149,10 @@ function getAuthorAvatar(author, config) {
105
149
  return `${config.siteUrl.replace(/\/$/, "")}${path}`;
106
150
  }
107
151
  function getAuthorSameAs(author) {
108
- return getAuthorSocialLinks(author).map((link) => link.href);
152
+ var _a, _b;
153
+ const derived = getAuthorSocialLinks(author).map((link) => link.href);
154
+ const explicit = (_b = (_a = author.sameAs) == null ? void 0 : _a.filter((url) => url.trim() !== "")) != null ? _b : [];
155
+ return [.../* @__PURE__ */ new Set([...derived, ...explicit])];
109
156
  }
110
157
  function normalizeHandle(value) {
111
158
  return value.replace(/^@/, "");
@@ -238,37 +285,129 @@ function getPersonSchemas(article, authors, config) {
238
285
  } else if (article.author) {
239
286
  resolvedAuthors = [{ name: article.author, slug: "", bio: "" }];
240
287
  }
241
- return resolvedAuthors.map((author) => __spreadValues(__spreadValues(__spreadValues({
242
- "@type": "Person",
243
- name: author.name
244
- }, author.url && { url: author.url }), getAuthorAvatar(author, config) && { image: getAuthorAvatar(author, config) }), getAuthorSameAs(author).length > 0 && { sameAs: getAuthorSameAs(author) }));
288
+ return resolvedAuthors.map((author) => {
289
+ const identityUrl = getAuthorIdentityUrl(author, config);
290
+ return __spreadValues(__spreadValues(__spreadValues(__spreadProps(__spreadValues({
291
+ "@type": "Person"
292
+ }, identityUrl && { "@id": getPersonId(identityUrl) }), {
293
+ name: author.name
294
+ }), author.url && { url: author.url }), getAuthorAvatar(author, config) && { image: getAuthorAvatar(author, config) }), getAuthorSameAs(author).length > 0 && { sameAs: getAuthorSameAs(author) });
295
+ });
245
296
  }
246
- function ArticleSEO({
297
+ function resolveModifiedAt(article, publishedAt, config) {
298
+ if (article.lastmod) return new Date(article.lastmod).toISOString();
299
+ return (config == null ? void 0 : config.lastmodFallback) === "none" ? void 0 : publishedAt;
300
+ }
301
+ function resolveSchemaImageUrl(image, articleUrl, config) {
302
+ if (/^https?:\/\//.test(image)) return image;
303
+ if (config) return resolveEntityUrl(image, config);
304
+ try {
305
+ return `${new URL(articleUrl).origin}/${image.replace(/^\/+/, "")}`;
306
+ } catch (e) {
307
+ return image;
308
+ }
309
+ }
310
+ function buildPublisher(siteName, siteLogo, config) {
311
+ if (config == null ? void 0 : config.organization) return { "@id": getOrganizationId(config) };
312
+ return __spreadValues({
313
+ "@type": "Organization",
314
+ name: siteName
315
+ }, siteLogo && { logo: { "@type": "ImageObject", url: siteLogo } });
316
+ }
317
+ function buildQuestionEntity(article, articleUrl, publishedAt) {
318
+ if (article.articleType !== "QAPage" || !article.answer) return void 0;
319
+ return __spreadProps(__spreadValues({
320
+ "@type": "Question",
321
+ name: article.title,
322
+ text: article.excerpt || article.title
323
+ }, publishedAt && { dateCreated: publishedAt }), {
324
+ answerCount: 1,
325
+ acceptedAnswer: { "@type": "Answer", text: article.answer, url: articleUrl }
326
+ });
327
+ }
328
+ function buildCommentFields(comments) {
329
+ var _a;
330
+ const visible = (_a = comments == null ? void 0 : comments.filter((comment) => !comment.isDeleted && !comment.parentId)) != null ? _a : [];
331
+ if (visible.length === 0) return {};
332
+ return {
333
+ commentCount: visible.length,
334
+ comment: visible.map((comment) => ({
335
+ "@type": "Comment",
336
+ text: comment.body,
337
+ dateCreated: comment.createdAt,
338
+ author: { "@type": "Person", name: comment.authorName }
339
+ }))
340
+ };
341
+ }
342
+ function buildTopicFields(article, config) {
343
+ var _a, _b;
344
+ const about = (_a = article.about) == null ? void 0 : _a.map((entity) => __spreadValues({
345
+ "@type": "Thing",
346
+ name: entity.name
347
+ }, entity.sameAs && { sameAs: entity.sameAs }));
348
+ const citation = (_b = article.citation) == null ? void 0 : _b.map((source) => __spreadValues({
349
+ "@type": "CreativeWork",
350
+ name: source.name
351
+ }, source.url && { url: source.url }));
352
+ const selectors = config == null ? void 0 : config.speakableSelectors;
353
+ return __spreadValues(__spreadValues(__spreadValues({}, (about == null ? void 0 : about.length) && { about }), (citation == null ? void 0 : citation.length) && { citation }), (selectors == null ? void 0 : selectors.length) && {
354
+ speakable: { "@type": "SpeakableSpecification", cssSelector: selectors }
355
+ });
356
+ }
357
+ function buildArticleSchema({
247
358
  article,
248
359
  articleUrl,
249
360
  siteName,
250
361
  siteLogo,
251
- showAuthor = true,
252
- authors,
253
- config
362
+ showAuthor,
363
+ personSchemas,
364
+ config,
365
+ comments
254
366
  }) {
367
+ var _a, _b;
255
368
  const publishedAt = article.date ? new Date(article.date).toISOString() : void 0;
256
- const modifiedAt = article.lastmod ? new Date(article.lastmod).toISOString() : publishedAt;
257
- const personSchemas = getPersonSchemas(article, authors, config);
258
- const structuredData = __spreadValues(__spreadValues(__spreadValues(__spreadProps(__spreadValues(__spreadValues(__spreadValues({
369
+ const modifiedAt = resolveModifiedAt(article, publishedAt, config);
370
+ const mainEntity = buildQuestionEntity(article, articleUrl, publishedAt);
371
+ return __spreadValues(__spreadValues(__spreadValues(__spreadProps(__spreadValues(__spreadValues(__spreadValues(__spreadProps(__spreadValues(__spreadProps(__spreadValues(__spreadValues(__spreadValues({
259
372
  "@context": "https://schema.org",
260
373
  "@type": article.articleType || "Article",
261
374
  mainEntityOfPage: { "@type": "WebPage", "@id": articleUrl },
262
375
  headline: article.title,
263
- image: article.featuredImage ? { "@type": "ImageObject", url: article.featuredImage } : void 0
376
+ image: article.featuredImage ? {
377
+ "@type": "ImageObject",
378
+ url: resolveSchemaImageUrl(article.featuredImage, articleUrl, config)
379
+ } : void 0
264
380
  }, publishedAt && { datePublished: publishedAt }), modifiedAt && { dateModified: modifiedAt }), showAuthor && personSchemas.length > 0 && { author: personSchemas }), {
265
- publisher: __spreadValues({
266
- "@type": "Organization",
267
- name: siteName
268
- }, siteLogo && { logo: { "@type": "ImageObject", url: siteLogo } }),
269
- description: article.excerpt,
381
+ publisher: buildPublisher(siteName, siteLogo, config),
382
+ description: article.excerpt
383
+ }), article.answer && { abstract: article.answer }), {
270
384
  articleSection: article.category
271
- }), 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 } });
385
+ }), ((_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 } }), {
386
+ inLanguage: (_b = config == null ? void 0 : config.language) != null ? _b : "en",
387
+ isAccessibleForFree: (config == null ? void 0 : config.isAccessibleForFree) !== false
388
+ }), buildTopicFields(article, config)), buildCommentFields(comments)), mainEntity && { mainEntity });
389
+ }
390
+ function ArticleSEO({
391
+ article,
392
+ articleUrl,
393
+ siteName,
394
+ siteLogo,
395
+ showAuthor = true,
396
+ authors,
397
+ config,
398
+ comments
399
+ }) {
400
+ const personSchemas = getPersonSchemas(article, authors, config);
401
+ const structuredData = buildArticleSchema({
402
+ article,
403
+ articleUrl,
404
+ siteName,
405
+ siteLogo,
406
+ showAuthor,
407
+ personSchemas,
408
+ config,
409
+ comments
410
+ });
272
411
  return /* @__PURE__ */ jsxs3(Fragment2, { children: [
273
412
  /* @__PURE__ */ jsx3(
274
413
  "script",
@@ -365,6 +504,65 @@ function CollectionPageSchema({
365
504
  }
366
505
  );
367
506
  }
507
+ function OrganizationSchema({ config }) {
508
+ var _a, _b, _c, _d, _e;
509
+ const org = config.organization;
510
+ if (!org) return null;
511
+ const schema = __spreadValues(__spreadValues(__spreadValues(__spreadValues({
512
+ "@context": "https://schema.org",
513
+ "@type": (_a = org.type) != null ? _a : "Organization",
514
+ "@id": getOrganizationId(config),
515
+ name: (_b = org.name) != null ? _b : config.siteName,
516
+ url: (_c = org.url) != null ? _c : config.siteUrl.replace(/\/$/, "")
517
+ }, org.logo && {
518
+ logo: { "@type": "ImageObject", url: resolveEntityUrl(org.logo, config) }
519
+ }), ((_d = org.description) != null ? _d : config.description) && {
520
+ description: (_e = org.description) != null ? _e : config.description
521
+ }), org.sameAs && org.sameAs.length > 0 && { sameAs: org.sameAs }), org.parentOrganization && {
522
+ parentOrganization: {
523
+ "@type": "Organization",
524
+ name: org.parentOrganization.name,
525
+ url: org.parentOrganization.url
526
+ }
527
+ });
528
+ return /* @__PURE__ */ jsx3(
529
+ "script",
530
+ {
531
+ type: "application/ld+json",
532
+ dangerouslySetInnerHTML: { __html: JSON.stringify(schema) }
533
+ }
534
+ );
535
+ }
536
+ function WebSiteSchema({ config }) {
537
+ const org = config.organization;
538
+ if (!org) return null;
539
+ const siteUrl = config.siteUrl.replace(/\/$/, "");
540
+ const template = org.searchUrlTemplate;
541
+ const schema = __spreadValues(__spreadValues({
542
+ "@context": "https://schema.org",
543
+ "@type": "WebSite",
544
+ "@id": getWebSiteId(config),
545
+ name: config.siteName,
546
+ url: siteUrl,
547
+ publisher: { "@id": getOrganizationId(config) }
548
+ }, config.description && { description: config.description }), template && {
549
+ potentialAction: {
550
+ "@type": "SearchAction",
551
+ target: {
552
+ "@type": "EntryPoint",
553
+ urlTemplate: resolveEntityUrl(template, config)
554
+ },
555
+ "query-input": "required name=search_term_string"
556
+ }
557
+ });
558
+ return /* @__PURE__ */ jsx3(
559
+ "script",
560
+ {
561
+ type: "application/ld+json",
562
+ dangerouslySetInnerHTML: { __html: JSON.stringify(schema) }
563
+ }
564
+ );
565
+ }
368
566
 
369
567
  // src/ArticleSearchBar.tsx
370
568
  import { Search, X } from "lucide-react";
@@ -840,26 +1038,6 @@ function LatestArticlesSection({
840
1038
  ] }) });
841
1039
  }
842
1040
 
843
- // src/articlesConfig.ts
844
- var DEFAULT_PAGE_SIZE = 6;
845
- var DEFAULT_CATEGORIES_PAGE_SIZE = 8;
846
- var DEFAULT_LAYOUT = [
847
- "hero",
848
- "search",
849
- "featured",
850
- "latest",
851
- "categories"
852
- ];
853
- function breadcrumbsAreEnabled(config) {
854
- var _a;
855
- return config.breadcrumbs !== false && ((_a = config.breadcrumbs) == null ? void 0 : _a.show) !== false;
856
- }
857
- function getBreadcrumbsConfig(config) {
858
- var _a;
859
- if (config.breadcrumbs === false) return {};
860
- return (_a = config.breadcrumbs) != null ? _a : {};
861
- }
862
-
863
1041
  // src/useArticles.ts
864
1042
  import { useState as useState3, useCallback, useEffect as useEffect3, useRef } from "react";
865
1043
  function useArticles(initialArticles, initialCategories) {
@@ -1562,17 +1740,28 @@ function CtaViewTracker({ ctaId, articleSlug, config, children }) {
1562
1740
 
1563
1741
  // src/AuthorArticlesPage.tsx
1564
1742
  import { Fragment as Fragment4, jsx as jsx18, jsxs as jsxs17 } from "react/jsx-runtime";
1565
- function getPersonSchema(author, config, articleCount) {
1743
+ function getKnowsAbout(author, articles) {
1744
+ var _a;
1745
+ if ((_a = author.knowsAbout) == null ? void 0 : _a.length) return [...author.knowsAbout];
1746
+ return [...new Set(articles.flatMap((article) => {
1747
+ var _a2;
1748
+ return (_a2 = article.categories) != null ? _a2 : [];
1749
+ }))].filter(Boolean);
1750
+ }
1751
+ function getPersonSchema(author, config, articleCount, knowsAbout) {
1566
1752
  var _a, _b;
1567
- return __spreadProps(__spreadValues(__spreadValues({
1753
+ const pageUrl = `${config.siteUrl.replace(/\/$/, "")}/articles/authors/${author.slug}`;
1754
+ const identityUrl = (_a = getAuthorIdentityUrl(author, config)) != null ? _a : pageUrl;
1755
+ const authorUrl = (_b = author.url) != null ? _b : pageUrl;
1756
+ return __spreadProps(__spreadValues(__spreadValues(__spreadValues(__spreadValues({
1568
1757
  "@context": "https://schema.org",
1569
1758
  "@type": "Person",
1759
+ "@id": getPersonId(identityUrl),
1570
1760
  name: author.name,
1571
1761
  description: author.bio,
1572
- url: (_a = author.url) != null ? _a : getAuthorUrl(author)
1573
- }, getAuthorAvatar(author, config) && { image: getAuthorAvatar(author, config) }), getAuthorSameAs(author).length > 0 && { sameAs: getAuthorSameAs(author) }), {
1574
- knowsAbout: config.siteName,
1575
- mainEntityOfPage: (_b = author.url) != null ? _b : `${config.siteUrl.replace(/\/$/, "")}/articles/authors/${author.slug}`,
1762
+ url: authorUrl
1763
+ }, 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) } }), {
1764
+ mainEntityOfPage: pageUrl,
1576
1765
  interactionStatistic: {
1577
1766
  "@type": "InteractionCounter",
1578
1767
  interactionType: "https://schema.org/WriteAction",
@@ -1725,7 +1914,9 @@ function AuthorArticlesPage({
1725
1914
  {
1726
1915
  type: "application/ld+json",
1727
1916
  dangerouslySetInnerHTML: {
1728
- __html: JSON.stringify(getPersonSchema(author, config, articleCount))
1917
+ __html: JSON.stringify(
1918
+ getPersonSchema(author, config, articleCount, getKnowsAbout(author, articles))
1919
+ )
1729
1920
  }
1730
1921
  }
1731
1922
  ),
@@ -2056,10 +2247,32 @@ function ArticleTOC({ toc, className }) {
2056
2247
  );
2057
2248
  }
2058
2249
 
2250
+ // src/ArticleAnswer.tsx
2251
+ import { jsx as jsx24, jsxs as jsxs23 } from "react/jsx-runtime";
2252
+ function ArticleAnswer({
2253
+ article,
2254
+ label = "The short answer",
2255
+ className
2256
+ }) {
2257
+ var _a;
2258
+ if (!((_a = article.answer) == null ? void 0 : _a.trim())) return null;
2259
+ return /* @__PURE__ */ jsxs23(
2260
+ "aside",
2261
+ {
2262
+ className: `mb-8 rounded-lg border-l-4 border-primary bg-muted/40 px-6 py-4 ${className != null ? className : ""}`,
2263
+ style: { borderLeftWidth: "4px" },
2264
+ children: [
2265
+ /* @__PURE__ */ jsx24("p", { className: "mb-2 text-sm font-semibold uppercase tracking-wide text-muted-foreground", children: label }),
2266
+ /* @__PURE__ */ jsx24("p", { className: "text-base leading-relaxed", children: article.answer })
2267
+ ]
2268
+ }
2269
+ );
2270
+ }
2271
+
2059
2272
  // src/ScrollToTop.tsx
2060
2273
  import { useEffect as useEffect5, useState as useState5 } from "react";
2061
2274
  import { ArrowUp } from "lucide-react";
2062
- import { jsx as jsx24 } from "react/jsx-runtime";
2275
+ import { jsx as jsx25 } from "react/jsx-runtime";
2063
2276
  function ScrollToTop() {
2064
2277
  const [visible, setVisible] = useState5(false);
2065
2278
  useEffect5(() => {
@@ -2068,13 +2281,13 @@ function ScrollToTop() {
2068
2281
  return () => globalThis.removeEventListener("scroll", onScroll);
2069
2282
  }, []);
2070
2283
  if (!visible) return null;
2071
- return /* @__PURE__ */ jsx24(
2284
+ return /* @__PURE__ */ jsx25(
2072
2285
  "button",
2073
2286
  {
2074
2287
  "aria-label": "Scroll to top",
2075
2288
  onClick: () => globalThis.scrollTo({ top: 0, behavior: "smooth" }),
2076
2289
  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",
2077
- children: /* @__PURE__ */ jsx24(ArrowUp, { className: "h-5 w-5" })
2290
+ children: /* @__PURE__ */ jsx25(ArrowUp, { className: "h-5 w-5" })
2078
2291
  }
2079
2292
  );
2080
2293
  }
@@ -2085,7 +2298,7 @@ import { useCallback as useCallback2, useEffect as useEffect6, useState as useSt
2085
2298
 
2086
2299
  // src/CommentForm.tsx
2087
2300
  import { useState as useState6 } from "react";
2088
- import { jsx as jsx25, jsxs as jsxs23 } from "react/jsx-runtime";
2301
+ import { jsx as jsx26, jsxs as jsxs24 } from "react/jsx-runtime";
2089
2302
  var MAX_LENGTH = 2e3;
2090
2303
  var WARN_THRESHOLD = 1800;
2091
2304
  function submitLabel(submitting, parentId) {
@@ -2124,8 +2337,8 @@ function CommentForm({ articleSlug, parentId, onSuccess, onCancel }) {
2124
2337
  }
2125
2338
  });
2126
2339
  }
2127
- return /* @__PURE__ */ jsxs23("form", { onSubmit: handleSubmit, className: "space-y-2", children: [
2128
- /* @__PURE__ */ jsx25(
2340
+ return /* @__PURE__ */ jsxs24("form", { onSubmit: handleSubmit, className: "space-y-2", children: [
2341
+ /* @__PURE__ */ jsx26(
2129
2342
  "textarea",
2130
2343
  {
2131
2344
  value: body,
@@ -2138,13 +2351,13 @@ function CommentForm({ articleSlug, parentId, onSuccess, onCancel }) {
2138
2351
  "aria-label": parentId ? "Reply text" : "Comment text"
2139
2352
  }
2140
2353
  ),
2141
- remaining <= MAX_LENGTH - WARN_THRESHOLD && /* @__PURE__ */ jsxs23("p", { className: `text-xs ${remaining < 0 ? "text-destructive" : "text-muted-foreground"}`, children: [
2354
+ remaining <= MAX_LENGTH - WARN_THRESHOLD && /* @__PURE__ */ jsxs24("p", { className: `text-xs ${remaining < 0 ? "text-destructive" : "text-muted-foreground"}`, children: [
2142
2355
  remaining,
2143
2356
  " characters remaining"
2144
2357
  ] }),
2145
- error && /* @__PURE__ */ jsx25("p", { className: "text-xs text-destructive", children: error }),
2146
- /* @__PURE__ */ jsxs23("div", { className: "flex gap-2", children: [
2147
- /* @__PURE__ */ jsx25(
2358
+ error && /* @__PURE__ */ jsx26("p", { className: "text-xs text-destructive", children: error }),
2359
+ /* @__PURE__ */ jsxs24("div", { className: "flex gap-2", children: [
2360
+ /* @__PURE__ */ jsx26(
2148
2361
  "button",
2149
2362
  {
2150
2363
  type: "submit",
@@ -2153,7 +2366,7 @@ function CommentForm({ articleSlug, parentId, onSuccess, onCancel }) {
2153
2366
  children: submitLabel(submitting, parentId)
2154
2367
  }
2155
2368
  ),
2156
- onCancel && /* @__PURE__ */ jsx25(
2369
+ onCancel && /* @__PURE__ */ jsx26(
2157
2370
  "button",
2158
2371
  {
2159
2372
  type: "button",
@@ -2168,7 +2381,7 @@ function CommentForm({ articleSlug, parentId, onSuccess, onCancel }) {
2168
2381
 
2169
2382
  // src/CommentItem.tsx
2170
2383
  import { useState as useState7 } from "react";
2171
- import { Fragment as Fragment5, jsx as jsx26, jsxs as jsxs24 } from "react/jsx-runtime";
2384
+ import { Fragment as Fragment5, jsx as jsx27, jsxs as jsxs25 } from "react/jsx-runtime";
2172
2385
  function relativeTime(iso) {
2173
2386
  const diff = Date.now() - new Date(iso).getTime();
2174
2387
  const minutes = Math.floor(diff / 6e4);
@@ -2206,15 +2419,15 @@ function CommentItem({
2206
2419
  }
2207
2420
  });
2208
2421
  }
2209
- return /* @__PURE__ */ jsxs24("div", { className: `${isReply ? "ml-8 border-l-2 border-border pl-4" : ""}`, children: [
2210
- /* @__PURE__ */ jsx26("div", { className: "rounded-lg bg-muted/40 p-3 space-y-1", children: comment.isDeleted ? /* @__PURE__ */ jsx26("p", { className: "text-sm italic text-muted-foreground", children: "Comment removed" }) : /* @__PURE__ */ jsxs24(Fragment5, { children: [
2211
- /* @__PURE__ */ jsxs24("div", { className: "flex items-center justify-between gap-2", children: [
2212
- /* @__PURE__ */ jsx26("span", { className: "text-sm font-medium text-foreground", children: comment.authorName }),
2213
- /* @__PURE__ */ jsx26("span", { className: "text-xs text-muted-foreground", children: relativeTime(comment.createdAt) })
2422
+ return /* @__PURE__ */ jsxs25("div", { className: `${isReply ? "ml-8 border-l-2 border-border pl-4" : ""}`, children: [
2423
+ /* @__PURE__ */ jsx27("div", { className: "rounded-lg bg-muted/40 p-3 space-y-1", children: comment.isDeleted ? /* @__PURE__ */ jsx27("p", { className: "text-sm italic text-muted-foreground", children: "Comment removed" }) : /* @__PURE__ */ jsxs25(Fragment5, { children: [
2424
+ /* @__PURE__ */ jsxs25("div", { className: "flex items-center justify-between gap-2", children: [
2425
+ /* @__PURE__ */ jsx27("span", { className: "text-sm font-medium text-foreground", children: comment.authorName }),
2426
+ /* @__PURE__ */ jsx27("span", { className: "text-xs text-muted-foreground", children: relativeTime(comment.createdAt) })
2214
2427
  ] }),
2215
- /* @__PURE__ */ jsx26("p", { className: "text-sm text-foreground whitespace-pre-wrap break-words", children: comment.body }),
2216
- /* @__PURE__ */ jsxs24("div", { className: "flex gap-3 pt-1", children: [
2217
- canReply && /* @__PURE__ */ jsx26(
2428
+ /* @__PURE__ */ jsx27("p", { className: "text-sm text-foreground whitespace-pre-wrap break-words", children: comment.body }),
2429
+ /* @__PURE__ */ jsxs25("div", { className: "flex gap-3 pt-1", children: [
2430
+ canReply && /* @__PURE__ */ jsx27(
2218
2431
  "button",
2219
2432
  {
2220
2433
  onClick: () => setShowReplyForm((v) => !v),
@@ -2222,7 +2435,7 @@ function CommentItem({
2222
2435
  children: showReplyForm ? "Cancel" : "Reply"
2223
2436
  }
2224
2437
  ),
2225
- canDelete && /* @__PURE__ */ jsx26(
2438
+ canDelete && /* @__PURE__ */ jsx27(
2226
2439
  "button",
2227
2440
  {
2228
2441
  onClick: handleDelete,
@@ -2233,7 +2446,7 @@ function CommentItem({
2233
2446
  )
2234
2447
  ] })
2235
2448
  ] }) }),
2236
- showReplyForm && /* @__PURE__ */ jsx26("div", { className: "mt-2 ml-2", children: /* @__PURE__ */ jsx26(
2449
+ showReplyForm && /* @__PURE__ */ jsx27("div", { className: "mt-2 ml-2", children: /* @__PURE__ */ jsx27(
2237
2450
  CommentForm,
2238
2451
  {
2239
2452
  articleSlug,
@@ -2245,7 +2458,7 @@ function CommentItem({
2245
2458
  onCancel: () => setShowReplyForm(false)
2246
2459
  }
2247
2460
  ) }),
2248
- replies.length > 0 && /* @__PURE__ */ jsx26("div", { className: "mt-2 space-y-2", children: replies.map((reply) => /* @__PURE__ */ jsx26(
2461
+ replies.length > 0 && /* @__PURE__ */ jsx27("div", { className: "mt-2 space-y-2", children: replies.map((reply) => /* @__PURE__ */ jsx27(
2249
2462
  CommentItem,
2250
2463
  {
2251
2464
  comment: __spreadProps(__spreadValues({}, reply), { replies: [] }),
@@ -2260,7 +2473,7 @@ function CommentItem({
2260
2473
  }
2261
2474
 
2262
2475
  // src/CommentThread.tsx
2263
- import { jsx as jsx27 } from "react/jsx-runtime";
2476
+ import { jsx as jsx28 } from "react/jsx-runtime";
2264
2477
  function CommentThread({
2265
2478
  comments,
2266
2479
  articleSlug,
@@ -2268,9 +2481,9 @@ function CommentThread({
2268
2481
  onRefresh
2269
2482
  }) {
2270
2483
  if (comments.length === 0) {
2271
- return /* @__PURE__ */ jsx27("p", { className: "text-sm text-muted-foreground text-center py-6", children: "No comments yet. Be the first to share your thoughts." });
2484
+ return /* @__PURE__ */ jsx28("p", { className: "text-sm text-muted-foreground text-center py-6", children: "No comments yet. Be the first to share your thoughts." });
2272
2485
  }
2273
- return /* @__PURE__ */ jsx27("div", { className: "space-y-4", children: comments.map((comment) => /* @__PURE__ */ jsx27(
2486
+ return /* @__PURE__ */ jsx28("div", { className: "space-y-4", children: comments.map((comment) => /* @__PURE__ */ jsx28(
2274
2487
  CommentItem,
2275
2488
  {
2276
2489
  comment,
@@ -2283,7 +2496,7 @@ function CommentThread({
2283
2496
  }
2284
2497
 
2285
2498
  // src/CommentsSection.tsx
2286
- import { jsx as jsx28, jsxs as jsxs25 } from "react/jsx-runtime";
2499
+ import { jsx as jsx29, jsxs as jsxs26 } from "react/jsx-runtime";
2287
2500
  function CommentsSection({ articleSlug, config }) {
2288
2501
  var _a, _b, _c, _d, _e, _f;
2289
2502
  const { data: session, status } = useSession();
@@ -2315,10 +2528,10 @@ function CommentsSection({ articleSlug, config }) {
2315
2528
  if (!enabled) return null;
2316
2529
  const count = comments.length;
2317
2530
  const label = count === 1 ? "1 comment" : `${count} comments`;
2318
- return /* @__PURE__ */ jsxs25("section", { "aria-label": "Comments", className: "mt-12 pt-8 border-t border-border space-y-6", children: [
2319
- /* @__PURE__ */ jsx28("h2", { className: "text-xl font-semibold text-foreground", children: loading ? "Comments" : label }),
2320
- status === "authenticated" ? /* @__PURE__ */ jsx28(CommentForm, { articleSlug, onSuccess: fetchComments }) : /* @__PURE__ */ jsxs25("p", { className: "text-sm text-muted-foreground", children: [
2321
- /* @__PURE__ */ jsx28(
2531
+ return /* @__PURE__ */ jsxs26("section", { "aria-label": "Comments", className: "mt-12 pt-8 border-t border-border space-y-6", children: [
2532
+ /* @__PURE__ */ jsx29("h2", { className: "text-xl font-semibold text-foreground", children: loading ? "Comments" : label }),
2533
+ status === "authenticated" ? /* @__PURE__ */ jsx29(CommentForm, { articleSlug, onSuccess: fetchComments }) : /* @__PURE__ */ jsxs26("p", { className: "text-sm text-muted-foreground", children: [
2534
+ /* @__PURE__ */ jsx29(
2322
2535
  "button",
2323
2536
  {
2324
2537
  onClick: () => signIn(),
@@ -2329,8 +2542,8 @@ function CommentsSection({ articleSlug, config }) {
2329
2542
  " ",
2330
2543
  "to join the discussion."
2331
2544
  ] }),
2332
- fetchError && /* @__PURE__ */ jsx28("p", { className: "text-sm text-destructive", children: fetchError }),
2333
- loading ? /* @__PURE__ */ jsx28("p", { className: "text-sm text-muted-foreground", children: "Loading comments\u2026" }) : /* @__PURE__ */ jsx28(
2545
+ fetchError && /* @__PURE__ */ jsx29("p", { className: "text-sm text-destructive", children: fetchError }),
2546
+ loading ? /* @__PURE__ */ jsx29("p", { className: "text-sm text-muted-foreground", children: "Loading comments\u2026" }) : /* @__PURE__ */ jsx29(
2334
2547
  CommentThread,
2335
2548
  {
2336
2549
  comments,
@@ -2342,6 +2555,7 @@ function CommentsSection({ articleSlug, config }) {
2342
2555
  ] });
2343
2556
  }
2344
2557
  export {
2558
+ ArticleAnswer,
2345
2559
  ArticleBackLink,
2346
2560
  ArticleCard,
2347
2561
  ArticleCategoryGrid,
@@ -2370,16 +2584,23 @@ export {
2370
2584
  DEFAULT_CATEGORIES_PAGE_SIZE,
2371
2585
  DEFAULT_LAYOUT,
2372
2586
  DEFAULT_PAGE_SIZE,
2587
+ DEFAULT_TITLE_TEMPLATE,
2373
2588
  FAQPageSchema,
2374
2589
  FeaturedArticle,
2375
2590
  LatestArticles,
2376
2591
  LatestArticlesSection,
2592
+ OrganizationSchema,
2377
2593
  PaginationNav,
2378
2594
  RelatedArticlesSection,
2379
2595
  ScrollToTop,
2380
2596
  SeriesArticlesPage,
2597
+ WebSiteSchema,
2381
2598
  breadcrumbsAreEnabled,
2599
+ formatPageTitle,
2382
2600
  getBreadcrumbsConfig,
2601
+ getOrganizationId,
2602
+ getPersonId,
2603
+ getWebSiteId,
2383
2604
  useArticles
2384
2605
  };
2385
2606
  //# sourceMappingURL=index.js.map