@farming-labs/theme 0.1.10 → 0.1.11

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.
@@ -22,6 +22,8 @@ function buildCodeBlock(lang, code) {
22
22
  return `<div class="fd-ai-code-block"><div class="fd-ai-code-header">${lang ? `<div class="fd-ai-code-lang">${escapeHtml(lang)}</div>` : ""}<button class="fd-ai-code-copy" onclick="(function(btn){var code=btn.closest('.fd-ai-code-block').querySelector('code').textContent;navigator.clipboard.writeText(code).then(function(){btn.textContent='Copied!';setTimeout(function(){btn.textContent='Copy'},1500)})})(this)">Copy</button></div><pre><code>${highlighted}</code></pre></div>`;
23
23
  }
24
24
  function renderMarkdown(text) {
25
+ const codeBlockTokenBoundary = String.fromCharCode(0);
26
+ const codeBlockTokenPattern = new RegExp(`${codeBlockTokenBoundary}CB(\\d+)${codeBlockTokenBoundary}`, "g");
25
27
  const codeBlocks = [];
26
28
  let processed = text.replace(/```(\w*)\n([\s\S]*?)```/g, (_match, lang, code) => {
27
29
  codeBlocks.push(buildCodeBlock(lang, code));
@@ -51,7 +53,7 @@ function renderMarkdown(text) {
51
53
  }
52
54
  let result = output.join("\n");
53
55
  result = result.replace(/`([^`]+)`/g, "<code>$1</code>").replace(/\*\*(.*?)\*\*/g, "<strong>$1</strong>").replace(/(?<!\*)\*([^*]+)\*(?!\*)/g, "<em>$1</em>").replace(/\[([^\]]+)\]\(([^)]+)\)/g, "<a href=\"$2\">$1</a>").replace(/^### (.*$)/gm, "<h4>$1</h4>").replace(/^## (.*$)/gm, "<h3>$1</h3>").replace(/^# (.*$)/gm, "<h2>$1</h2>").replace(/^[-*] (.*$)/gm, "<div style=\"display:flex;gap:8px;padding:2px 0\"><span style=\"opacity:0.5\">•</span><span>$1</span></div>").replace(/^(\d+)\. (.*$)/gm, "<div style=\"display:flex;gap:8px;padding:2px 0\"><span style=\"opacity:0.5\">$1.</span><span>$2</span></div>").replace(/\n\n/g, "<div style=\"height:8px\"></div>").replace(/\n/g, "<br>");
54
- result = result.replace(/\x00CB(\d+)\x00/g, (_m, idx) => codeBlocks[Number(idx)]);
56
+ result = result.replace(codeBlockTokenPattern, (_m, idx) => codeBlocks[Number(idx)]);
55
57
  return result;
56
58
  }
57
59
  function escapeHtml(s) {
package/dist/docs-api.mjs CHANGED
@@ -1,9 +1,9 @@
1
1
  import { withLangInUrl } from "./i18n.mjs";
2
2
  import { getNextAppDir } from "./get-app-dir.mjs";
3
- import { performDocsSearch, resolveDocsI18n, resolveDocsLocale, resolveSearchRequestConfig } from "@farming-labs/docs";
4
3
  import fs from "node:fs";
5
4
  import path from "node:path";
6
5
  import matter from "gray-matter";
6
+ import { performDocsSearch, resolveDocsI18n, resolveDocsLocale, resolveSearchRequestConfig } from "@farming-labs/docs";
7
7
  import { createDocsMcpHttpHandler, createFilesystemDocsMcpSource } from "@farming-labs/docs/server";
8
8
 
9
9
  //#region src/docs-api.ts
@@ -5,29 +5,16 @@ import { DocsAIFeatures } from "./docs-ai-features.mjs";
5
5
  import { DocsCommandSearch } from "./docs-command-search.mjs";
6
6
  import { SidebarSearchWithAI } from "./sidebar-search-ai.mjs";
7
7
  import { LocaleThemeControl } from "./locale-theme-control.mjs";
8
+ import fs from "node:fs";
9
+ import path from "node:path";
10
+ import matter from "gray-matter";
8
11
  import { DocsLayout } from "fumadocs-ui/layouts/docs";
9
12
  import { Suspense } from "react";
10
13
  import { buildPageOpenGraph, buildPageTwitter } from "@farming-labs/docs";
11
14
  import { jsx, jsxs } from "react/jsx-runtime";
12
15
 
13
16
  //#region src/docs-layout.tsx
14
- function runtimeRequire(id) {
15
- const requireFn = eval("require");
16
- return requireFn(id);
17
- }
18
- function getNodeFs() {
19
- return runtimeRequire("node:fs");
20
- }
21
- function getNodePath() {
22
- return runtimeRequire("node:path");
23
- }
24
- function getMatter() {
25
- const module = runtimeRequire("gray-matter");
26
- return module.default ?? module;
27
- }
28
17
  function getNextAppDir(root) {
29
- const fs = getNodeFs();
30
- const path = getNodePath();
31
18
  if (fs.existsSync(path.join(root, "src", "app"))) return "src/app";
32
19
  return "app";
33
20
  }
@@ -38,8 +25,6 @@ function resolveIcon(iconKey, registry) {
38
25
  }
39
26
  /** Read frontmatter from a page.mdx file. */
40
27
  function readFrontmatter(filePath) {
41
- const fs = getNodeFs();
42
- const matter = getMatter();
43
28
  try {
44
29
  const { data } = matter(fs.readFileSync(filePath, "utf-8"));
45
30
  return data;
@@ -49,8 +34,6 @@ function readFrontmatter(filePath) {
49
34
  }
50
35
  /** Check if a directory has any subdirectories that contain page.mdx. */
51
36
  function hasChildPages(dir) {
52
- const fs = getNodeFs();
53
- const path = getNodePath();
54
37
  if (!fs.existsSync(dir)) return false;
55
38
  for (const name of fs.readdirSync(dir)) {
56
39
  const full = path.join(dir, name);
@@ -74,7 +57,6 @@ function resolveDocsLocaleContext(config, locale) {
74
57
  const entryBase = config.entry ?? "docs";
75
58
  const i18n = resolveDocsI18nConfig(getDocsI18n(config));
76
59
  const contentDir = config.contentDir;
77
- const path = getNodePath();
78
60
  function resolveContentDir(localeValue) {
79
61
  if (!contentDir) {
80
62
  const appDir = getNextAppDir(process.cwd());
@@ -99,8 +81,6 @@ function buildTree(config, ctx, flat = false) {
99
81
  const icons = config.icons;
100
82
  const ordering = config.ordering;
101
83
  const rootChildren = [];
102
- const fs = getNodeFs();
103
- const path = getNodePath();
104
84
  if (fs.existsSync(path.join(docsDir, "page.mdx"))) {
105
85
  const data = readFrontmatter(path.join(docsDir, "page.mdx"));
106
86
  rootChildren.push({
@@ -227,8 +207,6 @@ function localizeTreeUrls(tree, locale) {
227
207
  function buildLastModifiedMap(ctx) {
228
208
  const docsDir = ctx.docsDir;
229
209
  const map = {};
230
- const fs = getNodeFs();
231
- const path = getNodePath();
232
210
  function formatDate(date) {
233
211
  return date.toLocaleDateString("en-US", {
234
212
  year: "numeric",
@@ -258,8 +236,6 @@ function buildLastModifiedMap(ctx) {
258
236
  function buildDescriptionMap(ctx) {
259
237
  const docsDir = ctx.docsDir;
260
238
  const map = {};
261
- const fs = getNodeFs();
262
- const path = getNodePath();
263
239
  function scan(dir, slugParts) {
264
240
  if (!fs.existsSync(dir)) return;
265
241
  const pagePath = path.join(dir, "page.mdx");
@@ -1,10 +1,16 @@
1
+ import { createRequire } from "node:module";
2
+
1
3
  //#region src/serialize-icon.ts
4
+ /**
5
+ * Server-only helper to convert a ReactNode icon into an HTML string
6
+ * so it can be safely serialized across the server→client boundary.
7
+ */
8
+ const require = createRequire(import.meta.url);
2
9
  function serializeIcon(icon) {
3
10
  if (!icon) return void 0;
4
11
  if (typeof icon === "string") return icon;
5
12
  try {
6
- const runtimeRequire = eval("require");
7
- const { renderToStaticMarkup } = runtimeRequire("react-dom/server");
13
+ const { renderToStaticMarkup } = require("react-dom/server");
8
14
  return renderToStaticMarkup(icon);
9
15
  } catch {
10
16
  return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@farming-labs/theme",
3
- "version": "0.1.10",
3
+ "version": "0.1.11",
4
4
  "description": "Theme package for @farming-labs/docs — layout, provider, MDX components, and styles",
5
5
  "keywords": [
6
6
  "docs",
@@ -133,7 +133,7 @@
133
133
  "tsdown": "^0.20.3",
134
134
  "typescript": "^5.9.3",
135
135
  "vitest": "^3.2.4",
136
- "@farming-labs/docs": "0.1.10"
136
+ "@farming-labs/docs": "0.1.11"
137
137
  },
138
138
  "peerDependencies": {
139
139
  "@farming-labs/docs": ">=0.0.1",
package/styles/base.css CHANGED
@@ -650,3 +650,29 @@ figure.shiki:hover > div:first-child button {
650
650
  width: 16px;
651
651
  height: 16px;
652
652
  }
653
+
654
+ /* ─── Logical inset compatibility ─────────────────────────────────── */
655
+
656
+ /* Tailwind 4 currently emits `start-*` utilities for some imported Fumadocs
657
+ * logical inset classes, while the rendered docs layout still uses `inset-s-*`.
658
+ * Add the small alias set the layout relies on so sidebar connectors, active
659
+ * markers, and sticky sidebar placement keep working across themes. */
660
+ .inset-s-0 {
661
+ inset-inline-start: 0;
662
+ }
663
+
664
+ .inset-s-4 {
665
+ inset-inline-start: calc(var(--spacing) * 4);
666
+ }
667
+
668
+ .inset-e-0 {
669
+ inset-inline-end: 0;
670
+ }
671
+
672
+ .before\:inset-s-2\.5::before {
673
+ inset-inline-start: calc(var(--spacing) * 2.5);
674
+ }
675
+
676
+ .data-\[active\=true\]\:before\:inset-s-2\.5[data-active="true"]::before {
677
+ inset-inline-start: calc(var(--spacing) * 2.5);
678
+ }
@@ -17,6 +17,12 @@
17
17
  --color-fd-ring: hsl(45, 90%, 55%);
18
18
  }
19
19
 
20
+ .fd-sidebar {
21
+ --color-fd-muted: hsl(0 0% 96%);
22
+ --color-fd-secondary: hsl(0 0% 97%);
23
+ --color-fd-muted-foreground: hsl(0 0% 45%);
24
+ }
25
+
20
26
  /* ─── Description under title ──────────────────────────────────────── */
21
27
 
22
28
  .fd-page-description {
@@ -397,3 +403,30 @@
397
403
  .fd-feedback-status[data-status="success"] {
398
404
  color: var(--color-fd-primary);
399
405
  }
406
+
407
+ /* ─── Desktop docs layout (colorful theme) ──────────────────────── */
408
+
409
+ @media (min-width: 1024px) {
410
+ #nd-docs-layout.grid {
411
+ grid-template:
412
+ "sidebar header toc"
413
+ "sidebar toc-popover toc"
414
+ "sidebar main toc" 1fr /
415
+ var(--fd-sidebar-col)
416
+ minmax(0, 1fr)
417
+ var(--fd-toc-width) !important;
418
+ }
419
+
420
+ #nd-docs-layout [data-sidebar-placeholder] {
421
+ justify-self: stretch;
422
+ }
423
+
424
+ #nd-docs-layout article#nd-page {
425
+ max-width: none;
426
+ margin-inline: 0;
427
+ }
428
+
429
+ #nd-docs-layout article#nd-page > .prose {
430
+ max-width: none;
431
+ }
432
+ }