@fullstackdatasolutions/articles 1.1.0 → 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -5,6 +5,26 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [1.2.1] - 2026-08-13
9
+
10
+ ### Fixed
11
+
12
+ - **`extractToc`/`markdownToHtml` mangled or silently dropped headings containing inline JSX or a markdown link.** Both functions parse headings through a plain (non-MDX) `remark-parse`, unlike `renderMdxSource`'s real `@mdx-js/mdx` compilation used for the actual page body. Two distinct bugs stacked on top of each other:
13
+ - A heading with inline JSX (the reader-facing color-coded rating dot used throughout the article library, e.g. `### Rage <span style={{ color: '#3b82f6' }}>●</span>`) isn't valid CommonMark inline HTML - `style={{ ... }}` isn't legal HTML attribute syntax - so remark's HTML tokenizer failed to recognize the tag and fell back to literal text, leaking the raw JSX source into both the visible TOC label (`Rage <span style={{ color: '#3b82f6' }}>●`) and the `rehype-slug`-generated id, which then didn't match the id the real MDX-rendered heading gets in the page - so the TOC entry silently linked to an anchor that didn't exist in the DOM. Fixed by stripping inline tags (keeping their inner text/content) from heading lines before the plain-remark parse, so the extracted text and generated slug match what the real render produces for the same heading.
14
+ - `nodeTextValue` only read a heading's direct text-node children, never recursing into child elements - so a heading using a markdown link as part of its text (e.g. `### [Dwarf](/link)`, a common pattern for hub/spoke class-guide headings) had its link text silently dropped. With the JSX-in-heading bug's own text also gone, this frequently zeroed out both `id` and `text` entirely, causing `extractHeadingItem` to return `null` and the heading to vanish from the TOC completely rather than merely render with garbled text. Fixed by making `nodeTextValue` recurse into element children.
15
+
16
+ No consumer-facing API change - both fixes only affect the text/slugs `extractToc` and `markdownToHtml` produce for previously-broken headings. Verified against theroleplayersguild.com's live-rendered heading ids (which come from the separate, already-correct `renderMdxSource` pipeline) to confirm the corrected slugs now match.
17
+
18
+ ## [1.2.0] - 2026-08-12
19
+
20
+ ### Added
21
+
22
+ - **`Breadcrumb` accepts `containerClassName`.** New optional prop, default `'max-w-7xl'` (unchanged from before), applied to the breadcrumb `<ol>` in place of the previously hardcoded value. Lets a consuming site align the breadcrumb bar's content width to its own reading column instead of the package's fixed default. Found on theroleplayersguild.com, where the breadcrumb's `max-w-7xl` sat visibly wider than the article body's `max-w-3xl` column beneath it.
23
+
24
+ ### Fixed
25
+
26
+ - **`ArticleDetailHero` byline never rendered the configured author avatar.** It always showed a generic user icon even when a single `authors[]` entry had a real `avatar` set (and even though `RelatedArticlesSection`/`ArticleCard` byline avatars worked correctly from the same data). Now resolves and renders the avatar image for the single-author case, matching the existing `ArticleCard` pattern; falls back to the icon when no avatar is set or multiple authors are configured, so existing output is unchanged unless a consumer already had `authors[].avatar` configured.
27
+
8
28
  ## [1.1.0] - 2026-08-09
9
29
 
10
30
  ### Added
package/README.md CHANGED
@@ -194,6 +194,7 @@ export default async function Page({ params }: ArticlePageProps) {
194
194
  article={article}
195
195
  authors={authors}
196
196
  categoryBasePath="/articles/category"
197
+ config={siteConfig}
197
198
  />
198
199
  <ArticleViewTracker article={article} config={siteConfig} />
199
200
  {siteConfig.showToc !== false && article.toc && article.toc.length > 0 && (
package/dist/index.cjs CHANGED
@@ -265,6 +265,7 @@ function getDisplayItems(items) {
265
265
  function Breadcrumb({
266
266
  items,
267
267
  className = "",
268
+ containerClassName = "max-w-7xl",
268
269
  showSchema = true,
269
270
  separator = ">"
270
271
  }) {
@@ -276,7 +277,7 @@ function Breadcrumb({
276
277
  {
277
278
  "aria-label": "Breadcrumb",
278
279
  className: `border-b border-border bg-background/80 px-4 py-3 text-sm text-muted-foreground ${className}`,
279
- children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("ol", { className: "mx-auto flex max-w-7xl items-center gap-2 overflow-hidden", children: displayItems.map((item, index) => {
280
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("ol", { className: `mx-auto flex items-center gap-2 overflow-hidden ${containerClassName}`, children: displayItems.map((item, index) => {
280
281
  const isLast = index === displayItems.length - 1;
281
282
  const key = `${item.name}-${index}`;
282
283
  return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("li", { className: "flex min-w-0 items-center gap-2", children: [
@@ -1155,6 +1156,15 @@ var import_jsx_runtime13 = require("react/jsx-runtime");
1155
1156
  function toSlug(cat) {
1156
1157
  return cat.toLowerCase().replaceAll(/\s+/g, "-").replaceAll(/[^a-z0-9-]/g, "");
1157
1158
  }
1159
+ function resolveAvatarUrl(author, config) {
1160
+ if (!author.avatar) return null;
1161
+ if (author.avatar.startsWith("http://") || author.avatar.startsWith("https://")) {
1162
+ return author.avatar;
1163
+ }
1164
+ if (!(config == null ? void 0 : config.siteUrl)) return null;
1165
+ const siteUrl = config.siteUrl.replace(/\/$/, "");
1166
+ return `${siteUrl}/articles/authors/${author.slug}/${author.avatar.replace(/^\/+/, "")}`;
1167
+ }
1158
1168
  function ArticleDetailHero({
1159
1169
  article,
1160
1170
  categoryBasePath = "/articles/category",
@@ -1272,24 +1282,36 @@ function ArticleDetailHero({
1272
1282
  /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_lucide_react7.Clock, { className: "h-4 w-4" }),
1273
1283
  /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { children: article.readTime })
1274
1284
  ] }),
1275
- showConfiguredAuthors && /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: "0.5rem" }, children: [
1276
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_lucide_react7.User, { className: "h-4 w-4" }),
1277
- /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("span", { children: [
1278
- "By",
1279
- " ",
1280
- authors.map((author, index) => /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(import_react4.Fragment, { children: [
1281
- index > 0 && ", ",
1282
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
1283
- import_link8.default,
1284
- {
1285
- href: `/articles/authors/${author.slug}`,
1286
- className: "font-medium text-white underline-offset-4 hover:underline",
1287
- children: author.name
1288
- }
1289
- )
1290
- ] }, author.slug))
1291
- ] })
1292
- ] }),
1285
+ showConfiguredAuthors && (() => {
1286
+ const singleAvatarUrl = authors.length === 1 ? resolveAvatarUrl(authors[0], config) : null;
1287
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: "0.5rem" }, children: [
1288
+ singleAvatarUrl ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
1289
+ import_image5.default,
1290
+ {
1291
+ src: singleAvatarUrl,
1292
+ alt: "",
1293
+ width: 20,
1294
+ height: 20,
1295
+ className: "h-5 w-5 rounded-full object-cover"
1296
+ }
1297
+ ) : /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_lucide_react7.User, { className: "h-4 w-4" }),
1298
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("span", { children: [
1299
+ "By",
1300
+ " ",
1301
+ authors.map((author, index) => /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(import_react4.Fragment, { children: [
1302
+ index > 0 && ", ",
1303
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
1304
+ import_link8.default,
1305
+ {
1306
+ href: `/articles/authors/${author.slug}`,
1307
+ className: "font-medium text-white underline-offset-4 hover:underline",
1308
+ children: author.name
1309
+ }
1310
+ )
1311
+ ] }, author.slug))
1312
+ ] })
1313
+ ] });
1314
+ })(),
1293
1315
  showLegacyAuthor && /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: "0.5rem" }, children: [
1294
1316
  /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_lucide_react7.User, { className: "h-4 w-4" }),
1295
1317
  /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("span", { children: [