@farming-labs/theme 0.2.96 → 0.2.98

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.
@@ -35,6 +35,30 @@ function readFrontmatter(filePath) {
35
35
  return {};
36
36
  }
37
37
  }
38
+ function hasAuthoredPageTitle(source) {
39
+ const { content } = matter(source);
40
+ const lines = content.split(/\r?\n/);
41
+ let fence;
42
+ let previousTextLine = "";
43
+ for (const line of lines) {
44
+ const fenceMatch = line.match(/^\s*(`{3,}|~{3,})/);
45
+ if (fenceMatch) {
46
+ const marker = fenceMatch[1]?.[0];
47
+ fence = fence === marker ? void 0 : fence ?? marker;
48
+ previousTextLine = "";
49
+ continue;
50
+ }
51
+ if (fence) continue;
52
+ if (/^ {0,3}#(?:\s+|$)/.test(line) || /^\s*<h1(?:\s|>)/i.test(line)) return true;
53
+ if (previousTextLine && /^ {0,3}=+\s*$/.test(line)) return true;
54
+ previousTextLine = line.trim() && !line.trimStart().startsWith("<") ? line : "";
55
+ }
56
+ return false;
57
+ }
58
+ function resolveSidebarFolderName(themeName, slug, pageTitle) {
59
+ if (themeName !== "shadcn" || pageTitle.trim().toLowerCase() !== "introduction") return pageTitle;
60
+ return slug.split(/[-_]/).filter(Boolean).map((part) => part[0]?.toUpperCase() + part.slice(1)).join(" ");
61
+ }
38
62
  /** Check if a directory has any subdirectories that contain page.mdx. */
39
63
  function isWithinDir(candidate, target) {
40
64
  const relative = path.relative(target, candidate);
@@ -194,12 +218,13 @@ function buildTree(config, ctx, flat = false) {
194
218
  const url = publicDocsRoute(ctx, slug);
195
219
  const icon = resolveIcon(data.icon, icons);
196
220
  const displayName = data.title ?? name.replace(/-/g, " ");
221
+ const folderName = resolveSidebarFolderName(config.theme?.name, name, displayName);
197
222
  const hasChildren = hasChildPages(full, excludedDirs);
198
223
  if (data.hidden === true) {
199
224
  if (!hasChildren) return null;
200
225
  return {
201
226
  type: "folder",
202
- name: displayName,
227
+ name: folderName,
203
228
  icon,
204
229
  children: scanDir(full, slug, slugOrder),
205
230
  ...flat ? {
@@ -212,7 +237,7 @@ function buildTree(config, ctx, flat = false) {
212
237
  const folderChildren = scanDir(full, slug, slugOrder);
213
238
  return {
214
239
  type: "folder",
215
- name: displayName,
240
+ name: folderName,
216
241
  icon,
217
242
  index: {
218
243
  type: "page",
@@ -380,6 +405,28 @@ function buildDescriptionMap(config, ctx) {
380
405
  scan(docsDir, []);
381
406
  return map;
382
407
  }
408
+ /** Build titles only for pages whose MDX body does not already author an h1. */
409
+ function buildGeneratedTitleMap(config, ctx) {
410
+ const docsDir = ctx.docsDir;
411
+ const map = {};
412
+ const excludedDirs = getExcludedDocsDirs(config, ctx);
413
+ function scan(dir, slugParts) {
414
+ if (!fs.existsSync(dir)) return;
415
+ if (isExcludedDir(dir, excludedDirs)) return;
416
+ const pagePath = findDocsPageFile(dir);
417
+ if (pagePath) {
418
+ const source = fs.readFileSync(pagePath, "utf-8");
419
+ const { data } = matter(source);
420
+ if (typeof data.title === "string" && !hasAuthoredPageTitle(source)) map[publicDocsRoute(ctx, slugParts)] = data.title;
421
+ }
422
+ for (const name of fs.readdirSync(dir)) {
423
+ const full = path.join(dir, name);
424
+ if (fs.statSync(full).isDirectory()) scan(full, [...slugParts, name]);
425
+ }
426
+ }
427
+ scan(docsDir, []);
428
+ return map;
429
+ }
383
430
  function buildReadingTimeMap(config, ctx, options) {
384
431
  const docsDir = ctx.docsDir;
385
432
  const map = {};
@@ -702,6 +749,7 @@ function createDocsLayout(config, options) {
702
749
  }
703
750
  const lastModifiedMap = buildLastModifiedMap(config, localeContext);
704
751
  const descriptionMap = buildDescriptionMap(config, localeContext);
752
+ const generatedTitleMap = config.theme?.name === "shadcn" ? buildGeneratedTitleMap(config, localeContext) : {};
705
753
  const readingTimeMap = buildReadingTimeMap(config, localeContext, {
706
754
  enabledByDefault: readingTimeEnabledByDefault,
707
755
  wordsPerMinute: readingTimeWordsPerMinute,
@@ -820,6 +868,7 @@ function createDocsLayout(config, options) {
820
868
  readingTimeMap,
821
869
  structuredDataMap,
822
870
  llmsTxtEnabled,
871
+ generatedTitleMap,
823
872
  descriptionMap,
824
873
  feedbackEnabled: feedbackConfig.enabled,
825
874
  feedbackQuestion: feedbackConfig.question,
@@ -78,6 +78,8 @@ interface DocsPageClientProps {
78
78
  lastUpdatedPosition?: "footer" | "below-title";
79
79
  /** Whether llms.txt is enabled — shows links in footer */
80
80
  llmsTxtEnabled?: boolean;
81
+ /** Frontmatter titles for pages whose MDX body does not author an h1 */
82
+ generatedTitleMap?: Record<string, string>;
81
83
  /** Map of pathname → frontmatter description */
82
84
  descriptionMap?: Record<string, string>;
83
85
  /** Frontmatter description to display below the page title (overrides descriptionMap) */
@@ -134,6 +136,7 @@ declare function DocsPageClient({
134
136
  lastUpdatedLabel,
135
137
  lastUpdatedPosition,
136
138
  llmsTxtEnabled,
139
+ generatedTitleMap,
137
140
  descriptionMap,
138
141
  description,
139
142
  feedbackEnabled,
@@ -255,7 +255,7 @@ function findThreadlineTocActionsContainer() {
255
255
  }
256
256
  return toc.parentElement ?? toc;
257
257
  }
258
- function DocsPageClient({ tocEnabled, tocStyle = "default", breadcrumbEnabled = true, changelogBasePath, entry = "docs", publicPath, locale, copyMarkdown = false, copyMarkdownFormat, copyMarkdownIncludeTitle, copyMarkdownLabel, copyMarkdownCopiedLabel, openDocs = false, openDocsProviders, openDocsTarget, openDocsPrompt, pageActionsPosition = "below-title", pageActionsAlignment = "left", githubUrl, contentDir, githubBranch = "main", githubDirectory, editOnGithubUrl, lastModifiedMap, lastModified: lastModifiedProp, readingTimeMap, readingTime: readingTimeProp, readingTimeFormat = "long", previousPage, nextPage, structuredDataMap, structuredData: structuredDataProp, readingTimeEnabled = false, lastUpdatedEnabled = true, lastUpdatedLabel = "Last updated", lastUpdatedPosition = "footer", llmsTxtEnabled = false, descriptionMap, description, feedbackEnabled = false, feedbackQuestion, feedbackPlaceholder, feedbackRequireComment, feedbackPositiveLabel, feedbackNegativeLabel, feedbackSubmitLabel, feedbackSuccessMessage, feedbackErrorMessage, feedbackOnFeedback, analytics = false, children }) {
258
+ function DocsPageClient({ tocEnabled, tocStyle = "default", breadcrumbEnabled = true, changelogBasePath, entry = "docs", publicPath, locale, copyMarkdown = false, copyMarkdownFormat, copyMarkdownIncludeTitle, copyMarkdownLabel, copyMarkdownCopiedLabel, openDocs = false, openDocsProviders, openDocsTarget, openDocsPrompt, pageActionsPosition = "below-title", pageActionsAlignment = "left", githubUrl, contentDir, githubBranch = "main", githubDirectory, editOnGithubUrl, lastModifiedMap, lastModified: lastModifiedProp, readingTimeMap, readingTime: readingTimeProp, readingTimeFormat = "long", previousPage, nextPage, structuredDataMap, structuredData: structuredDataProp, readingTimeEnabled = false, lastUpdatedEnabled = true, lastUpdatedLabel = "Last updated", lastUpdatedPosition = "footer", llmsTxtEnabled = false, generatedTitleMap, descriptionMap, description, feedbackEnabled = false, feedbackQuestion, feedbackPlaceholder, feedbackRequireComment, feedbackPositiveLabel, feedbackNegativeLabel, feedbackSubmitLabel, feedbackSuccessMessage, feedbackErrorMessage, feedbackOnFeedback, analytics = false, children }) {
259
259
  const fdTocStyle = tocStyle === "directional" ? "clerk" : void 0;
260
260
  const [toc, setToc] = useState([]);
261
261
  const [titlePortalHost, setTitlePortalHost] = useState(null);
@@ -268,8 +268,9 @@ function DocsPageClient({ tocEnabled, tocStyle = "default", breadcrumbEnabled =
268
268
  const activeLocale = resolveClientLocale(searchParams, locale);
269
269
  const resolvedPublicPath = normalizePublicDocsPath(publicPath, entry);
270
270
  const llmsLangQuery = activeLocale ? `?lang=${encodeURIComponent(activeLocale)}` : "";
271
- const pageDescription = description ?? descriptionMap?.[pathname.replace(/\/$/, "") || "/"];
272
271
  const normalizedPath = (browserPathname || pathname).replace(/\/$/, "") || "/";
272
+ const pageTitle = generatedTitleMap?.[normalizedPath];
273
+ const pageDescription = description ?? descriptionMap?.[normalizedPath];
273
274
  const isChangelogRoute = !!(changelogBasePath && (normalizedPath === changelogBasePath || normalizedPath.startsWith(`${changelogBasePath}/`)));
274
275
  const matchedReadingTime = readingTimeMap?.[normalizedPath];
275
276
  const structuredDataJson = !isChangelogRoute ? structuredDataProp ?? structuredDataMap?.[normalizedPath] : void 0;
@@ -495,7 +496,7 @@ function DocsPageClient({ tocEnabled, tocStyle = "default", breadcrumbEnabled =
495
496
  ]
496
497
  }, "below-title") : void 0;
497
498
  const decoratedChildren = children;
498
- const needsTitleDecorationsPortal = !!titleDescription || !!belowTitleBlock;
499
+ const needsTitleDecorationsPortal = !pageTitle && (!!titleDescription || !!belowTitleBlock);
499
500
  useEffect(() => {
500
501
  if (!needsTitleDecorationsPortal) {
501
502
  setTitlePortalHost(null);
@@ -521,6 +522,16 @@ function DocsPageClient({ tocEnabled, tocStyle = "default", breadcrumbEnabled =
521
522
  }) : null;
522
523
  const titleDecorationsPortal = titleDecorations && titlePortalHost ? createPortal(titleDecorations, titlePortalHost, "title-decorations") : null;
523
524
  const titleDecorationsFallback = titleDecorations && !titlePortalHost ? titleDecorations : null;
525
+ const generatedPageHeader = pageTitle ? /* @__PURE__ */ jsxs("div", {
526
+ className: "fd-generated-page-header not-prose",
527
+ children: [/* @__PURE__ */ jsx("h1", {
528
+ className: "fd-page-title",
529
+ children: pageTitle
530
+ }), /* @__PURE__ */ jsx(TitleDecorations, {
531
+ description: titleDescription,
532
+ belowTitle: belowTitleBlock
533
+ })]
534
+ }) : null;
524
535
  const titleControlsPortal = showActionsInToc && titleControlsPortalHost ? createPortal(/* @__PURE__ */ jsx(ThreadlinePageControls, {}), titleControlsPortalHost, "title-controls") : null;
525
536
  const tocActionsPortal = showActionsInToc && tocActionsPortalHost ? createPortal(/* @__PURE__ */ jsx("div", {
526
537
  className: "fd-actions-toc-portal not-prose",
@@ -604,11 +615,12 @@ function DocsPageClient({ tocEnabled, tocStyle = "default", breadcrumbEnabled =
604
615
  flexDirection: "column"
605
616
  },
606
617
  children: [
618
+ generatedPageHeader,
607
619
  /* @__PURE__ */ jsx("div", {
608
620
  style: { flex: 1 },
609
621
  children: renderedChildren
610
622
  }, "content"),
611
- titleDecorationsFallback,
623
+ !generatedPageHeader && titleDecorationsFallback,
612
624
  titleDecorationsPortal,
613
625
  !isChangelogRoute && feedbackEnabled && /* @__PURE__ */ jsx(DocsFeedback, {
614
626
  pathname: normalizedPath,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@farming-labs/theme",
3
- "version": "0.2.96",
3
+ "version": "0.2.98",
4
4
  "description": "Theme package for @farming-labs/docs — layout, provider, MDX components, and styles",
5
5
  "keywords": [
6
6
  "docs",
@@ -154,7 +154,7 @@
154
154
  "tsdown": "^0.20.3",
155
155
  "typescript": "^5.9.3",
156
156
  "vitest": "^4.1.8",
157
- "@farming-labs/docs": "0.2.96"
157
+ "@farming-labs/docs": "0.2.98"
158
158
  },
159
159
  "peerDependencies": {
160
160
  "@farming-labs/docs": ">=0.0.1",