@lexibase/rdx-next 4.1.0 → 5.0.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.
package/bin/cli.mjs CHANGED
@@ -74,7 +74,7 @@ export async function runRdxCli({ templateDir } = {}) {
74
74
  ['--help', '-h'].includes(commandArgs[0])
75
75
  ) {
76
76
  console.log(
77
- 'Usage: rdx check\nValidate static MDX links, section anchors and local public/ files without building the site.'
77
+ 'Usage: rdx check\nValidate static Markdown and MDX links, section anchors and local public/ files without building the site.'
78
78
  )
79
79
  return
80
80
  }
@@ -1,4 +1,4 @@
1
- import { lstat, mkdir, readFile, rm, writeFile } from 'node:fs/promises'
1
+ import { lstat, mkdir, readFile, rm, stat, writeFile } from 'node:fs/promises'
2
2
  import path from 'node:path'
3
3
 
4
4
  const defaultFiles = [
@@ -14,6 +14,7 @@ const generated = [
14
14
  'app/(rdx-locales)/[locale]/not-found.tsx',
15
15
  'app/(rdx-locales)/[locale]/docs/[[...slug]]/page.tsx',
16
16
  'app/(rdx-locales)/[locale]/rdx-search/[source]/route.ts',
17
+ 'app/(rdx-locales)/[locale]/docs/llms.txt/route.ts',
17
18
  ]
18
19
 
19
20
  /** Root layouts keep HTML language correct on the server, without middleware. */
@@ -34,6 +35,18 @@ export async function prepareLocaleRoutes(project, runtime, template) {
34
35
  }
35
36
  return current
36
37
  }
38
+ // An existing public index owns this URL. Keep it when upgrading a project.
39
+ const publicIndex = path.join(project.rootDir, 'public', 'docs', 'llms.txt')
40
+ const hasPublicIndex = await stat(publicIndex).then(
41
+ (entry) => entry.isFile(),
42
+ (error) => {
43
+ if (error.code === 'ENOENT' || error.code === 'ENOTDIR') return false
44
+ throw error
45
+ }
46
+ )
47
+ if (hasPublicIndex)
48
+ await rm(await safe('app/docs/llms.txt/route.ts'), { force: true })
49
+
37
50
  const marker = await safe('.rdx-localization.json')
38
51
  let owned = false
39
52
  try {
@@ -130,6 +143,23 @@ export async function generateStaticParams() {
130
143
  return project.localization!.locales.flatMap(locale => getSearchSources(getLocalizedProject(project, locale.path)).map(source => ({ locale: locale.path, ...source })))
131
144
  }
132
145
  export async function GET(_request: Request, { params }: { params: Promise<{ locale: string, source: string }> }) { const { locale, source } = await params; return getSearchResponse(getLocalizedProject(await getProject(), locale), source) }
146
+ `
147
+ )
148
+ await emit(
149
+ 'app/(rdx-locales)/[locale]/docs/llms.txt/route.ts',
150
+ `import { getLlmIndexResponse, getLocalizedProject } from '${provider}'
151
+ import { getProject } from '../../../../../rdx-project'
152
+ export const dynamic = 'force-static'
153
+ export const dynamicParams = false
154
+ export async function generateStaticParams() {
155
+ return (await getProject()).localization!.locales.map(locale => ({ locale: locale.path }))
156
+ }
157
+ export async function GET(_request: Request, { params }: { params: Promise<{ locale: string }> }) {
158
+ const project = await getProject()
159
+ const { locale } = await params
160
+ if (!project.localization?.locales.some(entry => entry.path === locale)) return new Response(null, { status: 404 })
161
+ return getLlmIndexResponse(getLocalizedProject(project, locale))
162
+ }
133
163
  `
134
164
  )
135
165
  await writeFile(marker, JSON.stringify({ version: 1 }) + '\n')
package/config.mjs CHANGED
@@ -49,7 +49,7 @@ async function contentFiles(directory) {
49
49
  const filename = path.join(directory, entry.name)
50
50
  if (entry.isDirectory()) return contentFiles(filename)
51
51
  return entry.isFile() &&
52
- (entry.name.endsWith('.mdx') || entry.name === '_category.json')
52
+ (/\.mdx?$/.test(entry.name) || entry.name === '_category.json')
53
53
  ? [filename]
54
54
  : []
55
55
  })
@@ -21,6 +21,17 @@ declare function getRdxSitemap(project: RdxProject, { documentPath, }?: {
21
21
  documentPath?: (source: RdxContentSource, page: RdxDocPage) => string;
22
22
  }): MetadataRoute.Sitemap;
23
23
 
24
+ type DocumentPath = (source: RdxContentSource, page: Pick<RdxDocPage, 'slug' | 'sourcePath'>) => string;
25
+ declare function getMarkdownHref(htmlPath: string): string;
26
+ declare function getLlmIndexResponse(project: RdxProject, documentPath?: DocumentPath): Response;
27
+ declare function getMarkdownPaths(project: RdxProject, documentPath?: DocumentPath): {
28
+ path: string[];
29
+ }[];
30
+ declare function parseMarkdownPath(path: string[]): string[] | undefined;
31
+ declare function getMarkdownResponse(project: RdxProject, path: string[]): Response;
32
+ declare function markdownNotFound(): Response;
33
+ declare function getDocumentMarkdownResponse(project: RdxProject, source: RdxContentSource | undefined, slug: string, documentPath?: DocumentPath): Response;
34
+
24
35
  /** Read metadata from the same content source and index used by the page. */
25
36
  declare function getDocumentMetadata({ project, slug, }: {
26
37
  project: RdxProject;
@@ -47,4 +58,4 @@ declare function DocsPage({ slug, project, }: {
47
58
  project: RdxProject;
48
59
  }): Promise<react.JSX.Element>;
49
60
 
50
- export { DocsPage, HomePage, NotFoundPage, RdxLayout, getDocumentMetadata, getPageMetadata, getRdxMetadata, getRdxRobots, getRdxSitemap, getSearchResponse, getSearchSources, getSourceRobots };
61
+ export { DocsPage, HomePage, NotFoundPage, RdxLayout, getDocumentMarkdownResponse, getDocumentMetadata, getLlmIndexResponse, getMarkdownHref, getMarkdownPaths, getMarkdownResponse, getPageMetadata, getRdxMetadata, getRdxRobots, getRdxSitemap, getSearchResponse, getSearchSources, getSourceRobots, markdownNotFound, parseMarkdownPath };
@@ -1,14 +1,15 @@
1
1
  // src/runtime/index.tsx
2
2
  import {
3
- getDocument as getDocument3,
3
+ getDocument as getDocument4,
4
4
  getDocsPaths,
5
- getSourceOptions as getSourceOptions4,
5
+ getSourceOptions as getSourceOptions5,
6
6
  resolveDocsRoute as resolveDocsRoute2,
7
- getLocalizedProject as getLocalizedProject2,
8
- getDocsHref as getDocsHref3,
7
+ getLocalizedProject as getLocalizedProject3,
8
+ getDocsHref as getDocsHref4,
9
9
  getLanguageLinks,
10
- isFallbackPage as isFallbackPage3
10
+ isFallbackPage as isFallbackPage4
11
11
  } from "@lexibase/rdx-core";
12
+ import { getEditPageHref } from "@lexibase/rdx-core/config";
12
13
  import { Button } from "@lexibase/rdx-ui/components/button";
13
14
  import { Callout } from "@lexibase/rdx-ui/components/callout";
14
15
  import { LanguageDropdown } from "@lexibase/rdx-ui/components/language-dropdown";
@@ -19,7 +20,9 @@ import { DocsSearch } from "@lexibase/rdx-ui/components/docs-search";
19
20
  import { Home } from "@lexibase/rdx-ui/components/home";
20
21
  import { SkipLink } from "@lexibase/rdx-ui/components/skip-link";
21
22
  import { FooterCredit } from "@lexibase/rdx-ui/components/footer-credit";
22
- import { Logo } from "@lexibase/rdx-ui/components/logo";
23
+ import { EditPageLink } from "@lexibase/rdx-ui/components/edit-page-link";
24
+ import { CopyPage } from "@lexibase/rdx-ui/components/copy-page";
25
+ import { BrandLogo } from "@lexibase/rdx-ui/components/brand-logo";
23
26
  import { getMdxComponents } from "@lexibase/rdx-ui/components/mdx-components";
24
27
  import { getMessages, formatMessage } from "@lexibase/rdx-ui/lib/i18n";
25
28
  import { LocaleProvider } from "@lexibase/rdx-ui/providers/locale-provider";
@@ -49,6 +52,7 @@ function compileDocumentation(source, components, links) {
49
52
  blockJS: true,
50
53
  blockDangerousJS: true,
51
54
  mdxOptions: {
55
+ format: links?.sourcePath.endsWith(".md") ? "md" : "mdx",
52
56
  remarkPlugins: [
53
57
  remarkGfm,
54
58
  remarkDocumentationComponents,
@@ -60,19 +64,164 @@ function compileDocumentation(source, components, links) {
60
64
  });
61
65
  }
62
66
 
67
+ // src/runtime/markdown.ts
68
+ import {
69
+ getContentSources,
70
+ getDocsHref,
71
+ getDocsIndex,
72
+ getDocument,
73
+ getLocalizedProject,
74
+ getSourceOptions,
75
+ isFallbackPage
76
+ } from "@lexibase/rdx-core";
77
+ import { exportMarkdownBody } from "@lexibase/rdx-core/mdx";
78
+ function publicUrl(project, path) {
79
+ const base = project.config.metadata.metadataBase;
80
+ return base ? new URL(path, base).href : path;
81
+ }
82
+ function indexPath(project) {
83
+ return `${project.locale ? `/${project.locale.path}` : ""}/docs/llms.txt`;
84
+ }
85
+ function defaultLocale(project) {
86
+ const locale = project.localization?.locales.find(
87
+ (entry) => entry.code === project.localization.defaultLocale
88
+ );
89
+ return !project.locale && locale ? getLocalizedProject(project, locale.path) : project;
90
+ }
91
+ function text(value) {
92
+ return value.replace(/\s+/g, " ").trim().replace(/[\\`*_[\]<>]/g, "\\$&");
93
+ }
94
+ function getMarkdownHref(htmlPath) {
95
+ return `/rdx-markdown${htmlPath}.md`;
96
+ }
97
+ function response(body, type, status = 200) {
98
+ return new Response(body, {
99
+ status,
100
+ headers: {
101
+ "Content-Type": `${type}; charset=utf-8`,
102
+ "X-Robots-Tag": "noindex, follow"
103
+ }
104
+ });
105
+ }
106
+ function getLlmIndexResponse(project, documentPath) {
107
+ project = defaultLocale(project);
108
+ const source = project.sources.find(
109
+ (entry) => entry.id !== "current" && entry.redirectTo === void 0 && entry.badge !== "Archived"
110
+ ) ?? project.sources[0];
111
+ const { metadata, description } = project.config;
112
+ const lines = [`# ${text(metadata.applicationName)} Documentation`, ""];
113
+ if (description) lines.push(`> ${text(description)}`, "");
114
+ if (source) {
115
+ for (const category of getDocsIndex(getSourceOptions(project, source))) {
116
+ const pages = category.pages.filter(
117
+ (page) => !isFallbackPage(project, source, page.sourcePath)
118
+ );
119
+ if (!pages.length) continue;
120
+ lines.push(`## ${text(category.title)}`, "");
121
+ for (const page of pages) {
122
+ const href = documentPath?.(source, page) ?? getDocsHref(project, source, page.slug);
123
+ lines.push(
124
+ `- [${text(page.title)}](<${publicUrl(project, getMarkdownHref(href))}>)${page.description ? `: ${text(page.description)}` : ""}`
125
+ );
126
+ }
127
+ lines.push("");
128
+ }
129
+ }
130
+ return response(lines.join("\n"), "text/plain");
131
+ }
132
+ function getMarkdownPaths(project, documentPath) {
133
+ const paths = /* @__PURE__ */ new Set();
134
+ for (const source of getContentSources(project)) {
135
+ if (source.redirectTo !== void 0) continue;
136
+ for (const category of getDocsIndex(getSourceOptions(project, source))) {
137
+ for (const page of category.pages) {
138
+ if (isFallbackPage(project, source, page.sourcePath)) continue;
139
+ const href = documentPath?.(source, page) ?? getDocsHref(project, source, page.slug);
140
+ paths.add(`${href.slice(1)}.md`);
141
+ }
142
+ }
143
+ }
144
+ return [...paths].map((path) => ({
145
+ path: path.split("/").map(decodeURIComponent)
146
+ }));
147
+ }
148
+ function parseMarkdownPath(path) {
149
+ const last = path.at(-1);
150
+ return last?.endsWith(".md") && last.length > 3 ? [...path.slice(0, -1), last.slice(0, -3)] : void 0;
151
+ }
152
+ function getMarkdownResponse(project, path) {
153
+ const segments = parseMarkdownPath(path);
154
+ if (!segments) return markdownNotFound();
155
+ if (project.localization) {
156
+ const locale = segments.shift();
157
+ if (!project.localization.locales.some((entry) => entry.path === locale))
158
+ return markdownNotFound();
159
+ project = getLocalizedProject(project, locale);
160
+ }
161
+ if (segments.shift() !== "docs" || !segments.length) return markdownNotFound();
162
+ const edition = project.sources.find(
163
+ (source) => source.routePrefix && source.routePrefix === segments[0]
164
+ );
165
+ if (edition) segments.shift();
166
+ return getDocumentMarkdownResponse(
167
+ project,
168
+ edition ?? project.sources[0],
169
+ segments.join("/")
170
+ );
171
+ }
172
+ function markdownNotFound() {
173
+ return response(null, "text/markdown", 404);
174
+ }
175
+ function getDocumentMarkdownResponse(project, source, slug, documentPath) {
176
+ if (!source || !slug || source.redirectTo !== void 0)
177
+ return markdownNotFound();
178
+ const options = getSourceOptions(project, source);
179
+ const document = getDocument(slug, options);
180
+ if (!document || isFallbackPage(project, source, document.sourcePath))
181
+ return markdownNotFound();
182
+ const pages = getDocsIndex(options).flatMap((category) => category.pages);
183
+ const bySlug = new Map(pages.map((page2) => [page2.slug, page2]));
184
+ const href = (page2) => publicUrl(
185
+ project,
186
+ documentPath?.(source, page2) ?? getDocsHref(project, source, page2.slug)
187
+ );
188
+ const page = bySlug.get(document.slug);
189
+ const frontmatter = {
190
+ title: page.title,
191
+ ...page.description ? { description: page.description } : {},
192
+ url: href(document),
193
+ docs_index: publicUrl(project, indexPath(project))
194
+ };
195
+ const fields = Object.entries(frontmatter).map(
196
+ ([key, value]) => `${key}: ${JSON.stringify(value)}`
197
+ );
198
+ const body = exportMarkdownBody(document.content, {
199
+ sourceRoot: options.docsDir,
200
+ sourcePath: document.sourcePath,
201
+ virtualPaths: !!options.fallbackDocsDir,
202
+ documents: pages,
203
+ documentHref: (slug2) => href(bySlug.get(slug2))
204
+ });
205
+ return response(`---
206
+ ${fields.join("\n")}
207
+ ---
208
+
209
+ ${body}`, "text/markdown");
210
+ }
211
+
63
212
  // src/runtime/index.tsx
64
213
  import {
65
214
  createRdxProject,
66
215
  getProjectDocsPaths,
67
- getLocalizedProject as getLocalizedProject3
216
+ getLocalizedProject as getLocalizedProject4
68
217
  } from "@lexibase/rdx-core";
69
218
 
70
219
  // src/runtime/search.ts
71
220
  import {
72
- getDocsIndex,
73
- getDocument,
74
- getSourceOptions,
75
- getDocsHref
221
+ getDocsIndex as getDocsIndex2,
222
+ getDocument as getDocument2,
223
+ getSourceOptions as getSourceOptions2,
224
+ getDocsHref as getDocsHref2
76
225
  } from "@lexibase/rdx-core";
77
226
  import { buildSearchIndex } from "@lexibase/rdx-core/search";
78
227
  function getSearchSources(project) {
@@ -88,15 +237,16 @@ function getSearchResponse(project, key) {
88
237
  if (!source) return new Response(null, { status: 404, headers });
89
238
  if (source.fallbackOnly)
90
239
  return Response.json(buildSearchIndex([]), { headers });
91
- const { docsDir } = getSourceOptions(project, source);
240
+ const { docsDir } = getSourceOptions2(project, source);
92
241
  const options = { docsDir };
93
- const documents = getDocsIndex(options).flatMap(
242
+ const documents = getDocsIndex2(options).flatMap(
94
243
  (category) => category.pages.map((page) => ({
95
244
  title: page.title,
96
245
  description: page.description,
97
246
  category: category.title,
98
- href: getDocsHref(project, source, page.slug),
99
- content: getDocument(page.slug, options)?.content ?? ""
247
+ href: getDocsHref2(project, source, page.slug),
248
+ sourcePath: page.sourcePath,
249
+ content: getDocument2(page.slug, options)?.content ?? ""
100
250
  }))
101
251
  );
102
252
  return Response.json(buildSearchIndex(documents), { headers });
@@ -104,10 +254,10 @@ function getSearchResponse(project, key) {
104
254
 
105
255
  // src/runtime/indexing.ts
106
256
  import {
107
- getDocsIndex as getDocsIndex2,
108
- getSourceOptions as getSourceOptions2,
109
- getContentSources,
110
- isFallbackPage
257
+ getDocsIndex as getDocsIndex3,
258
+ getSourceOptions as getSourceOptions3,
259
+ getContentSources as getContentSources2,
260
+ isFallbackPage as isFallbackPage2
111
261
  } from "@lexibase/rdx-core";
112
262
  function getSourceRobots(project, source) {
113
263
  const published = project.sources.some(
@@ -136,11 +286,11 @@ function getRdxSitemap(project, {
136
286
  if (!base) return [];
137
287
  const urls = /* @__PURE__ */ new Set();
138
288
  if (project.config.homepage?.enabled) urls.add(new URL("/", base).href);
139
- for (const source of getContentSources(project)) {
289
+ for (const source of getContentSources2(project)) {
140
290
  if (!getSourceRobots(project, source).index) continue;
141
- for (const category of getDocsIndex2(getSourceOptions2(project, source))) {
291
+ for (const category of getDocsIndex3(getSourceOptions3(project, source))) {
142
292
  for (const page of category.pages) {
143
- if (isFallbackPage(project, source, page.sourcePath)) continue;
293
+ if (isFallbackPage2(project, source, page.sourcePath)) continue;
144
294
  urls.add(new URL(documentPath(source, page), base).href);
145
295
  }
146
296
  }
@@ -151,11 +301,11 @@ function getRdxSitemap(project, {
151
301
  // src/runtime/metadata.ts
152
302
  import {
153
303
  resolveDocsRoute,
154
- getLocalizedProject,
155
- getDocsHref as getDocsHref2,
156
- isFallbackPage as isFallbackPage2,
157
- getDocument as getDocument2,
158
- getSourceOptions as getSourceOptions3
304
+ getLocalizedProject as getLocalizedProject2,
305
+ getDocsHref as getDocsHref3,
306
+ isFallbackPage as isFallbackPage3,
307
+ getDocument as getDocument3,
308
+ getSourceOptions as getSourceOptions4
159
309
  } from "@lexibase/rdx-core";
160
310
  import { notFound } from "next/navigation";
161
311
  function getDocumentMetadata({
@@ -170,24 +320,24 @@ function getDocumentMetadata({
170
320
  if (!route.slug) return { robots: { index: false, follow: true } };
171
321
  if (!route.document) notFound();
172
322
  const page = route.categories.flatMap((category) => category.pages).find((page2) => page2.slug === route.slug);
173
- const fallback = isFallbackPage2(project, route.source, page.sourcePath);
323
+ const fallback = isFallbackPage3(project, route.source, page.sourcePath);
174
324
  const primary = project.localization?.locales.find(
175
325
  (locale) => locale.code === project.localization.defaultLocale
176
326
  );
177
- const canonicalProject = fallback ? getLocalizedProject(project, primary.path) : project;
327
+ const canonicalProject = fallback ? getLocalizedProject2(project, primary.path) : project;
178
328
  const base = project.config.metadata.metadataBase;
179
329
  const languages = project.localization && base ? Object.fromEntries(
180
330
  project.localization.locales.flatMap((locale) => {
181
- const view = getLocalizedProject(project, locale.path);
331
+ const view = getLocalizedProject2(project, locale.path);
182
332
  const source = view.sources.find(
183
333
  (entry) => entry.id === route.source.id
184
334
  );
185
- return !source.fallbackOnly && getDocument2(page.slug, {
186
- docsDir: getSourceOptions3(view, source).docsDir
335
+ return !source.fallbackOnly && getDocument3(page.slug, {
336
+ docsDir: getSourceOptions4(view, source).docsDir
187
337
  }) ? [
188
338
  [
189
339
  locale.code,
190
- new URL(getDocsHref2(view, source, page.slug), base).href
340
+ new URL(getDocsHref3(view, source, page.slug), base).href
191
341
  ]
192
342
  ] : [];
193
343
  })
@@ -198,7 +348,7 @@ function getDocumentMetadata({
198
348
  ...project.localization && base ? {
199
349
  alternates: {
200
350
  canonical: new URL(
201
- getDocsHref2(
351
+ getDocsHref3(
202
352
  canonicalProject,
203
353
  canonicalProject.sources.find(
204
354
  (entry) => entry.id === route.source.id
@@ -278,16 +428,15 @@ function RdxHeader({
278
428
  const messages = getMessages(config.lang);
279
429
  const { source, slug: currentSlug } = resolveDocsRoute2(project, slug);
280
430
  const options = project.sources.filter((edition) => edition.redirectTo === void 0).map((edition) => {
281
- const docs = getSourceOptions4(project, edition);
282
- const destination = currentSlug && getDocument3(currentSlug, docs) ? currentSlug : getDocsPaths(docs)[0];
431
+ const docs = getSourceOptions5(project, edition);
432
+ const destination = currentSlug && getDocument4(currentSlug, docs) ? currentSlug : getDocsPaths(docs)[0];
283
433
  return {
284
434
  id: edition.id,
285
435
  label: edition.id === "current" ? messages.current : formatMessage(messages.version, { version: edition.label }),
286
436
  kind: edition.id === "current" ? "live" : edition.badge === "Archived" ? "archived" : "version",
287
- href: getDocsHref3(project, edition, destination)
437
+ href: getDocsHref4(project, edition, destination)
288
438
  };
289
439
  });
290
- const BrandLogo = config.navbar?.logo ?? Logo;
291
440
  return /* @__PURE__ */ jsx(
292
441
  Navbar,
293
442
  {
@@ -301,9 +450,9 @@ function RdxHeader({
301
450
  }
302
451
  ) : void 0,
303
452
  siteTitle: config.metadata.applicationName,
304
- logo: /* @__PURE__ */ jsx(BrandLogo, { className: "size-5" }),
305
- homeHref: config.homepage?.enabled ? "/" : getDocsHref3(project),
306
- docsHref: getDocsHref3(project, source),
453
+ logo: /* @__PURE__ */ jsx(BrandLogo, { source: config.navbar?.logo, className: "size-5" }),
454
+ homeHref: config.homepage?.enabled ? "/" : getDocsHref4(project),
455
+ docsHref: getDocsHref4(project, source),
307
456
  languageMenu: project.localization ? /* @__PURE__ */ jsx(
308
457
  LanguageDropdown,
309
458
  {
@@ -321,10 +470,9 @@ function RdxHeader({
321
470
  }
322
471
  function HomePage({ project }) {
323
472
  const { config } = project;
324
- if (!config.homepage?.enabled) redirect(getDocsHref3(project));
473
+ if (!config.homepage?.enabled) redirect(getDocsHref4(project));
325
474
  const homepage = config.homepage;
326
475
  const CustomHome = homepage.component;
327
- const BrandLogo = config.navbar?.logo ?? Logo;
328
476
  return /* @__PURE__ */ jsxs(Fragment, { children: [
329
477
  /* @__PURE__ */ jsx(RdxHeader, { project }),
330
478
  /* @__PURE__ */ jsx(
@@ -337,7 +485,13 @@ function HomePage({ project }) {
337
485
  Home,
338
486
  {
339
487
  title: homepage.title ?? config.metadata.applicationName,
340
- logo: /* @__PURE__ */ jsx(BrandLogo, { className: "size-52 hover:scale-105 hover:rotate-1 transition-[scale,rotation] duration-1000" }),
488
+ logo: /* @__PURE__ */ jsx(
489
+ BrandLogo,
490
+ {
491
+ source: config.navbar?.logo,
492
+ className: "size-52 hover:scale-105 hover:rotate-1 transition-[scale,rotation] duration-1000"
493
+ }
494
+ ),
341
495
  description: homepage.description,
342
496
  buttons: homepage.buttons
343
497
  }
@@ -349,7 +503,6 @@ function HomePage({ project }) {
349
503
  function NotFoundPage({ project }) {
350
504
  if (project.localization) {
351
505
  const { config, localization } = project;
352
- const BrandLogo = config.navbar?.logo ?? Logo;
353
506
  return /* @__PURE__ */ jsx(
354
507
  LocalizedNotFound,
355
508
  {
@@ -361,7 +514,7 @@ function NotFoundPage({ project }) {
361
514
  siteTitle: config.metadata.applicationName,
362
515
  github: config.navbar?.actions?.github,
363
516
  showToggleMode: config.navbar?.actions?.showToggleMode,
364
- logo: /* @__PURE__ */ jsx(BrandLogo, { className: "size-5" })
517
+ logo: /* @__PURE__ */ jsx(BrandLogo, { source: config.navbar?.logo, className: "size-5" })
365
518
  }
366
519
  }
367
520
  );
@@ -376,7 +529,7 @@ async function DocsPage({
376
529
  project
377
530
  }) {
378
531
  if (project.localization && !project.locale)
379
- permanentRedirect(getDocsHref3(project, project.sources[0], slug?.join("/")));
532
+ permanentRedirect(getDocsHref4(project, project.sources[0], slug?.join("/")));
380
533
  const messages = getMessages(project.config.lang);
381
534
  const {
382
535
  source,
@@ -386,7 +539,7 @@ async function DocsPage({
386
539
  } = resolveDocsRoute2(project, slug);
387
540
  if (source.redirectTo !== void 0)
388
541
  permanentRedirect(
389
- getDocsHref3(
542
+ getDocsHref4(
390
543
  project,
391
544
  project.sources.find(
392
545
  (entry) => entry.routePrefix === source.redirectTo
@@ -403,15 +556,16 @@ async function DocsPage({
403
556
  ] })
404
557
  ] });
405
558
  if (!document) notFound2();
406
- const fallback = isFallbackPage3(project, source, document.sourcePath);
559
+ const fallback = isFallbackPage4(project, source, document.sourcePath);
560
+ const editHref = fallback ? void 0 : getEditPageHref(project, source, document.sourcePath);
407
561
  const primaryLocale = (source.localization ?? project.localization)?.locales.find(
408
562
  (locale) => locale.code === project.localization.defaultLocale
409
563
  );
410
- const originalProject = fallback && primaryLocale ? getLocalizedProject2(project, primaryLocale.path) : project;
564
+ const originalProject = fallback && primaryLocale ? getLocalizedProject3(project, primaryLocale.path) : project;
411
565
  const contentSource = originalProject.sources.find(
412
566
  (entry) => entry.id === source.id
413
567
  );
414
- const contentOptions = getSourceOptions4(originalProject, contentSource);
568
+ const contentOptions = getSourceOptions5(originalProject, contentSource);
415
569
  const contentCategories = fallback ? resolveDocsRoute2(originalProject, slug).categories : categories;
416
570
  const contentLanguage = fallback ? primaryLocale.code : project.config.lang;
417
571
  const { content } = await compileDocumentation(
@@ -426,18 +580,20 @@ async function DocsPage({
426
580
  documents: contentCategories.flatMap((category2) => category2.pages)
427
581
  }
428
582
  );
429
- const title = typeof document.frontmatter.title === "string" ? document.frontmatter.title : currentSlug;
430
583
  const description = typeof document.frontmatter.description === "string" ? document.frontmatter.description : null;
431
- const hrefFor = (pageSlug) => getDocsHref3(project, source, pageSlug);
584
+ const hrefFor = (pageSlug) => getDocsHref4(project, source, pageSlug);
585
+ const markdownHref = fallback ? void 0 : getMarkdownHref(hrefFor(currentSlug));
432
586
  const sidebarCategories = categories.map((category2) => ({
433
587
  title: category2.title,
434
588
  links: category2.pages.map((page) => ({
435
- label: page.title,
589
+ label: page.navTitle ?? page.title,
590
+ highlighting: page.highlighting,
436
591
  href: hrefFor(page.slug)
437
592
  }))
438
593
  }));
439
594
  const pages = categories.flatMap((category2) => category2.pages);
440
595
  const index = pages.findIndex((page) => page.slug === currentSlug);
596
+ const title = pages[index]?.title ?? currentSlug;
441
597
  const previous = pages[index - 1];
442
598
  const next = pages[index + 1];
443
599
  const category = categories.find(
@@ -495,7 +651,7 @@ async function DocsPage({
495
651
  Button,
496
652
  {
497
653
  variant: "link",
498
- href: getDocsHref3(
654
+ href: getDocsHref4(
499
655
  originalProject,
500
656
  contentSource,
501
657
  currentSlug
@@ -529,7 +685,10 @@ async function DocsPage({
529
685
  /* @__PURE__ */ jsxs("header", { className: "mb-6", children: [
530
686
  source.badge && /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground mb-4", children: source.badge === "Archived" ? messages.archivedVersion : source.badge }),
531
687
  /* @__PURE__ */ jsx("span", { className: "text-xs text-zinc-600 font-semibold uppercase block -mb-0.5", children: category?.title }),
532
- /* @__PURE__ */ jsx("h1", { className: "text-4xl uppercase font-bold mb-2", children: title }),
688
+ /* @__PURE__ */ jsxs("div", { className: "mb-2 flex flex-wrap items-start gap-x-4 gap-y-2", children: [
689
+ /* @__PURE__ */ jsx("h1", { className: "min-w-0 max-w-full break-words text-4xl font-bold uppercase", children: title }),
690
+ markdownHref && /* @__PURE__ */ jsx(CopyPage, { href: markdownHref }, markdownHref)
691
+ ] }),
533
692
  description && /* @__PURE__ */ jsx("p", { className: "text-sm text-ring max-w-[726px]", children: description })
534
693
  ] }),
535
694
  /* @__PURE__ */ jsx("div", { className: "h-full w-full flex-1 max-w-[960px]", children: content })
@@ -537,6 +696,7 @@ async function DocsPage({
537
696
  }
538
697
  ),
539
698
  /* @__PURE__ */ jsxs("footer", { className: "flex flex-col", children: [
699
+ editHref && /* @__PURE__ */ jsx(EditPageLink, { href: editHref, lang: project.config.lang }),
540
700
  /* @__PURE__ */ jsxs("section", { className: "flex items-center justify-between", children: [
541
701
  previous ? /* @__PURE__ */ jsx(
542
702
  Button,
@@ -585,8 +745,13 @@ export {
585
745
  NotFoundPage,
586
746
  RdxLayout,
587
747
  createRdxProject,
748
+ getDocumentMarkdownResponse,
588
749
  getDocumentMetadata,
589
- getLocalizedProject3 as getLocalizedProject,
750
+ getLlmIndexResponse,
751
+ getLocalizedProject4 as getLocalizedProject,
752
+ getMarkdownHref,
753
+ getMarkdownPaths,
754
+ getMarkdownResponse,
590
755
  getPageMetadata,
591
756
  getProjectDocsPaths,
592
757
  getRdxMetadata,
@@ -594,5 +759,7 @@ export {
594
759
  getRdxSitemap,
595
760
  getSearchResponse,
596
761
  getSearchSources,
597
- getSourceRobots
762
+ getSourceRobots,
763
+ markdownNotFound,
764
+ parseMarkdownPath
598
765
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lexibase/rdx-next",
3
- "version": "4.1.0",
3
+ "version": "5.0.0",
4
4
  "description": "The Next.js runtime for RDX documentation projects.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -52,11 +52,11 @@
52
52
  "next-mdx-remote": "^6.0.0",
53
53
  "remark-gfm": "^4",
54
54
  "typescript": "6.0.3",
55
- "@lexibase/rdx-core": "2.1.0",
56
- "@lexibase/rdx-types": "2.1.0"
55
+ "@lexibase/rdx-types": "2.3.0",
56
+ "@lexibase/rdx-core": "3.0.0"
57
57
  },
58
58
  "peerDependencies": {
59
- "@lexibase/rdx-ui": "^3.3.0",
59
+ "@lexibase/rdx-ui": "^3.5.0",
60
60
  "next": "^16.4.2",
61
61
  "react": "^19.3.1",
62
62
  "react-dom": "^19.3.1"
@@ -81,9 +81,9 @@
81
81
  "next": "^16.4.2",
82
82
  "react": "19.3.1",
83
83
  "react-dom": "19.3.1",
84
- "@lexibase/rdx-ui": "3.3.0",
85
- "@rdx/typescript-config": "1.0.0",
86
- "@rdx/eslint-config": "1.0.0"
84
+ "@lexibase/rdx-ui": "3.5.0",
85
+ "@rdx/eslint-config": "1.0.0",
86
+ "@rdx/typescript-config": "1.0.0"
87
87
  },
88
88
  "scripts": {
89
89
  "dev": "tsup src/index.ts src/runtime/index.tsx --format esm --dts --watch --external @lexibase/rdx-core --external @lexibase/rdx-types --external @lexibase/rdx-ui --external next --external react --external react-dom --external next-mdx-remote",
@@ -0,0 +1,8 @@
1
+ import { getLlmIndexResponse } from '@lexibase/rdx-next/runtime'
2
+ import { getProject } from '../../../rdx-project'
3
+
4
+ export const dynamic = 'force-static'
5
+
6
+ export async function GET() {
7
+ return getLlmIndexResponse(await getProject())
8
+ }
@@ -0,0 +1,19 @@
1
+ import {
2
+ getMarkdownPaths,
3
+ getMarkdownResponse,
4
+ } from '@lexibase/rdx-next/runtime'
5
+ import { getProject } from '../../../rdx-project'
6
+
7
+ export const dynamic = 'force-static'
8
+ export const dynamicParams = false
9
+
10
+ export async function generateStaticParams() {
11
+ return getMarkdownPaths(await getProject())
12
+ }
13
+
14
+ export async function GET(
15
+ _request: Request,
16
+ { params }: { params: Promise<{ path: string[] }> }
17
+ ) {
18
+ return getMarkdownResponse(await getProject(), (await params).path)
19
+ }