Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 20 additions & 1 deletion source.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,26 @@ export const docs = defineDocs({
docs: {
async: isDevelopment,
postprocess: {
includeProcessedMarkdown: true,
includeProcessedMarkdown: {
mdxAsPlaceholder: [
"Accordion",
"AccordionGroup",
"Callout",
"Card",
"CardGroup",
"CodeGroup",
"Frame",
"Info",
"Mermaid",
"Note",
"Step",
"Steps",
"Tab",
"Tabs",
"Tip",
"Warning",
],
},
},
},
});
Expand Down
2 changes: 1 addition & 1 deletion src/lib/llms.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ export function getLLMSectionConfig(section?: string) {
return llmsSectionConfigs[section as LLMSection];
}

export function getPagesForLLMSection(pages: LLMPageLike[], section?: string) {
export function getPagesForLLMSection<TPage extends LLMPageLike>(pages: TPage[], section?: string) {
const config = getLLMSectionConfig(section);
if (!config) return pages;

Expand Down
40 changes: 40 additions & 0 deletions src/lib/markdown-alternate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { DOCS_BASE } from "./url-base";

const MARKDOWN_ROUTE_PREFIX = `${DOCS_BASE}/llms.mdx/docs`;

function normalizeDocsPath(pathname: string): string {
const normalized = pathname.replace(/\/+$/, "");
return normalized || DOCS_BASE;
}

export function buildMarkdownAlternatePath(pathname: string): string {
const normalized = normalizeDocsPath(pathname);
if (normalized === DOCS_BASE) return MARKDOWN_ROUTE_PREFIX;
return `${normalized}.md`;
}

export function buildHtmlPathFromMarkdownRoute(slugs: string[]): string {
if (slugs.length === 0) return DOCS_BASE;
return `${DOCS_BASE}/${slugs.join("/")}`;
}

export function appendHeaderValue(headers: Headers, name: string, value: string) {
const current = headers.get(name);
if (!current) {
headers.set(name, value);
return;
}

const values = current.split(",").map((entry) => entry.trim().toLowerCase());
if (!values.includes(value.toLowerCase())) {
headers.set(name, `${current}, ${value}`);
}
}

export function appendVaryAccept(headers: Headers) {
appendHeaderValue(headers, "Vary", "Accept");
}

export function buildMarkdownAlternateLinkHeader(pathname: string): string {
return `<${buildMarkdownAlternatePath(pathname)}>; rel="alternate"; type="text/markdown"`;
}
75 changes: 69 additions & 6 deletions src/lib/source.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import { InferPageType, loader } from "fumadocs-core/source";
import {
renderPlaceholder,
type PlaceholderData,
} from "fumadocs-core/mdx-plugins/remark-llms.runtime";
import { docs } from "fumadocs-mdx:collections/server";
import { createElement, type SVGProps } from "react";
import {
Expand Down Expand Up @@ -134,17 +138,76 @@ export const source = loader({
},
});

function getStringAttribute(attributes: Record<string, unknown>, key: string): string | undefined {
const value = attributes[key];
return typeof value === "string" && value.trim() ? value.trim() : undefined;
}

function joinMarkdownBlocks(blocks: Array<string | undefined>) {
return blocks
.map((block) => block?.trim())
.filter(Boolean)
.join("\n\n");
}

function renderMarkdownBlock(blocks: Array<string | undefined>) {
const block = joinMarkdownBlocks(blocks);
return block ? `${block}\n\n` : "";
}

function renderCallout(label: string, data: PlaceholderData) {
const title = getStringAttribute(data.attributes, "title") ?? label;
return renderMarkdownBlock([`> **${title}**`, data.children]);
}

const markdownPlaceholderRenderers: Record<string, (data: PlaceholderData) => string> = {
Accordion: (data) =>
renderMarkdownBlock([
`## ${getStringAttribute(data.attributes, "title") ?? "Details"}`,
data.children,
]),
AccordionGroup: (data) => data.children,
Callout: (data) => renderCallout("Callout", data),
Card: (data) =>
renderMarkdownBlock([
`## ${getStringAttribute(data.attributes, "title") ?? "Link"}`,
getStringAttribute(data.attributes, "href"),
data.children,
]),
CardGroup: (data) => data.children,
CodeGroup: (data) => data.children,
Frame: (data) => data.children,
Info: (data) => renderCallout("Info", data),
Mermaid: (data) => `\`\`\`mermaid\n${data.children.trim()}\n\`\`\`\n\n`,
Note: (data) => renderCallout("Note", data),
Step: (data) =>
renderMarkdownBlock([
`## ${getStringAttribute(data.attributes, "title") ?? "Step"}`,
data.children,
]),
Steps: (data) => data.children,
Tab: (data) =>
renderMarkdownBlock([
`## ${getStringAttribute(data.attributes, "title") ?? "Tab"}`,
data.children,
]),
Tabs: (data) => data.children,
Tip: (data) => renderCallout("Tip", data),
Warning: (data) => renderCallout("Warning", data),
};

export async function getPageMarkdownText(
page: InferPageType<typeof source>,
_type: "raw" | "processed" = "processed",
) {
return page.data.getText("processed");
const processed = await renderPlaceholder(
await page.data.getText("processed"),
markdownPlaceholderRenderers,
);

return joinMarkdownBlocks([`# ${page.data.title}`, page.data.description, processed]);
}

export async function getLLMText(page: InferPageType<typeof source>) {
const processed = await getPageMarkdownText(page);

return `# ${page.data.title}

${processed}`;
return getPageMarkdownText(page);
}
64 changes: 57 additions & 7 deletions src/routeTree.gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,10 @@ import { Route as SplatRouteImport } from './routes/$'
import { Route as IndexRouteImport } from './routes/index'
import { Route as SdkIndexRouteImport } from './routes/sdk/index'
import { Route as SdkSplatRouteImport } from './routes/sdk/$'
import { Route as LlmsDotmdxDocsRouteImport } from './routes/llms[.]mdx.docs'
import { Route as ApiSearchRouteImport } from './routes/api/search'
import { Route as ApiFeedbackRouteImport } from './routes/api/feedback'
import { Route as LlmsDotmdxDocsIndexRouteImport } from './routes/llms[.]mdx.docs.index'
import { Route as LlmsDotmdxDocsSplatRouteImport } from './routes/llms[.]mdx.docs.$'
import { Route as ApiRawSplatRouteImport } from './routes/api/raw/$'

Expand Down Expand Up @@ -76,6 +78,11 @@ const SdkSplatRoute = SdkSplatRouteImport.update({
path: '/sdk/$',
getParentRoute: () => rootRouteImport,
} as any)
const LlmsDotmdxDocsRoute = LlmsDotmdxDocsRouteImport.update({
id: '/llms.mdx/docs',
path: '/llms.mdx/docs',
getParentRoute: () => rootRouteImport,
} as any)
const ApiSearchRoute = ApiSearchRouteImport.update({
id: '/api/search',
path: '/api/search',
Expand All @@ -86,10 +93,15 @@ const ApiFeedbackRoute = ApiFeedbackRouteImport.update({
path: '/api/feedback',
getParentRoute: () => rootRouteImport,
} as any)
const LlmsDotmdxDocsIndexRoute = LlmsDotmdxDocsIndexRouteImport.update({
id: '/',
path: '/',
getParentRoute: () => LlmsDotmdxDocsRoute,
} as any)
const LlmsDotmdxDocsSplatRoute = LlmsDotmdxDocsSplatRouteImport.update({
id: '/llms.mdx/docs/$',
path: '/llms.mdx/docs/$',
getParentRoute: () => rootRouteImport,
id: '/$',
path: '/$',
getParentRoute: () => LlmsDotmdxDocsRoute,
} as any)
const ApiRawSplatRoute = ApiRawSplatRouteImport.update({
id: '/api/raw/$',
Expand All @@ -108,10 +120,12 @@ export interface FileRoutesByFullPath {
'/sitemap.xml': typeof SitemapDotxmlRoute
'/api/feedback': typeof ApiFeedbackRoute
'/api/search': typeof ApiSearchRoute
'/llms.mdx/docs': typeof LlmsDotmdxDocsRouteWithChildren
'/sdk/$': typeof SdkSplatRoute
'/sdk/': typeof SdkIndexRoute
'/api/raw/$': typeof ApiRawSplatRoute
'/llms.mdx/docs/$': typeof LlmsDotmdxDocsSplatRoute
'/llms.mdx/docs/': typeof LlmsDotmdxDocsIndexRoute
}
export interface FileRoutesByTo {
'/': typeof IndexRoute
Expand All @@ -128,6 +142,7 @@ export interface FileRoutesByTo {
'/sdk': typeof SdkIndexRoute
'/api/raw/$': typeof ApiRawSplatRoute
'/llms.mdx/docs/$': typeof LlmsDotmdxDocsSplatRoute
'/llms.mdx/docs': typeof LlmsDotmdxDocsIndexRoute
}
export interface FileRoutesById {
__root__: typeof rootRouteImport
Expand All @@ -141,10 +156,12 @@ export interface FileRoutesById {
'/sitemap.xml': typeof SitemapDotxmlRoute
'/api/feedback': typeof ApiFeedbackRoute
'/api/search': typeof ApiSearchRoute
'/llms.mdx/docs': typeof LlmsDotmdxDocsRouteWithChildren
'/sdk/$': typeof SdkSplatRoute
'/sdk/': typeof SdkIndexRoute
'/api/raw/$': typeof ApiRawSplatRoute
'/llms.mdx/docs/$': typeof LlmsDotmdxDocsSplatRoute
'/llms.mdx/docs/': typeof LlmsDotmdxDocsIndexRoute
}
export interface FileRouteTypes {
fileRoutesByFullPath: FileRoutesByFullPath
Expand All @@ -159,10 +176,12 @@ export interface FileRouteTypes {
| '/sitemap.xml'
| '/api/feedback'
| '/api/search'
| '/llms.mdx/docs'
| '/sdk/$'
| '/sdk/'
| '/api/raw/$'
| '/llms.mdx/docs/$'
| '/llms.mdx/docs/'
fileRoutesByTo: FileRoutesByTo
to:
| '/'
Expand All @@ -179,6 +198,7 @@ export interface FileRouteTypes {
| '/sdk'
| '/api/raw/$'
| '/llms.mdx/docs/$'
| '/llms.mdx/docs'
id:
| '__root__'
| '/'
Expand All @@ -191,10 +211,12 @@ export interface FileRouteTypes {
| '/sitemap.xml'
| '/api/feedback'
| '/api/search'
| '/llms.mdx/docs'
| '/sdk/$'
| '/sdk/'
| '/api/raw/$'
| '/llms.mdx/docs/$'
| '/llms.mdx/docs/'
fileRoutesById: FileRoutesById
}
export interface RootRouteChildren {
Expand All @@ -208,10 +230,10 @@ export interface RootRouteChildren {
SitemapDotxmlRoute: typeof SitemapDotxmlRoute
ApiFeedbackRoute: typeof ApiFeedbackRoute
ApiSearchRoute: typeof ApiSearchRoute
LlmsDotmdxDocsRoute: typeof LlmsDotmdxDocsRouteWithChildren
SdkSplatRoute: typeof SdkSplatRoute
SdkIndexRoute: typeof SdkIndexRoute
ApiRawSplatRoute: typeof ApiRawSplatRoute
LlmsDotmdxDocsSplatRoute: typeof LlmsDotmdxDocsSplatRoute
}

declare module '@tanstack/react-router' {
Expand Down Expand Up @@ -286,6 +308,13 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof SdkSplatRouteImport
parentRoute: typeof rootRouteImport
}
'/llms.mdx/docs': {
id: '/llms.mdx/docs'
path: '/llms.mdx/docs'
fullPath: '/llms.mdx/docs'
preLoaderRoute: typeof LlmsDotmdxDocsRouteImport
parentRoute: typeof rootRouteImport
}
'/api/search': {
id: '/api/search'
path: '/api/search'
Expand All @@ -300,12 +329,19 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof ApiFeedbackRouteImport
parentRoute: typeof rootRouteImport
}
'/llms.mdx/docs/': {
id: '/llms.mdx/docs/'
path: '/'
fullPath: '/llms.mdx/docs/'
preLoaderRoute: typeof LlmsDotmdxDocsIndexRouteImport
parentRoute: typeof LlmsDotmdxDocsRoute
}
'/llms.mdx/docs/$': {
id: '/llms.mdx/docs/$'
path: '/llms.mdx/docs/$'
path: '/$'
fullPath: '/llms.mdx/docs/$'
preLoaderRoute: typeof LlmsDotmdxDocsSplatRouteImport
parentRoute: typeof rootRouteImport
parentRoute: typeof LlmsDotmdxDocsRoute
}
'/api/raw/$': {
id: '/api/raw/$'
Expand All @@ -317,6 +353,20 @@ declare module '@tanstack/react-router' {
}
}

interface LlmsDotmdxDocsRouteChildren {
LlmsDotmdxDocsSplatRoute: typeof LlmsDotmdxDocsSplatRoute
LlmsDotmdxDocsIndexRoute: typeof LlmsDotmdxDocsIndexRoute
}

const LlmsDotmdxDocsRouteChildren: LlmsDotmdxDocsRouteChildren = {
LlmsDotmdxDocsSplatRoute: LlmsDotmdxDocsSplatRoute,
LlmsDotmdxDocsIndexRoute: LlmsDotmdxDocsIndexRoute,
}

const LlmsDotmdxDocsRouteWithChildren = LlmsDotmdxDocsRoute._addFileChildren(
LlmsDotmdxDocsRouteChildren,
)

const rootRouteChildren: RootRouteChildren = {
IndexRoute: IndexRoute,
SplatRoute: SplatRoute,
Expand All @@ -329,10 +379,10 @@ const rootRouteChildren: RootRouteChildren = {
SitemapDotxmlRoute: SitemapDotxmlRoute,
ApiFeedbackRoute: ApiFeedbackRoute,
ApiSearchRoute: ApiSearchRoute,
LlmsDotmdxDocsRoute: LlmsDotmdxDocsRouteWithChildren,
SdkSplatRoute: SdkSplatRoute,
SdkIndexRoute: SdkIndexRoute,
ApiRawSplatRoute: ApiRawSplatRoute,
LlmsDotmdxDocsSplatRoute: LlmsDotmdxDocsSplatRoute,
}
export const routeTree = rootRouteImport
._addFileChildren(rootRouteChildren)
Expand Down
18 changes: 15 additions & 3 deletions src/routes/$.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {
} from "@/lib/metadata";
import { getSdkContextFromRouterPath, SDK_CONTEXT_SLUGS } from "@/lib/sdk-navigation";
import { buildDocsApiPath } from "@/lib/url-base";
import { buildMarkdownAlternatePath } from "@/lib/markdown-alternate";

import { staticCacheMiddleware } from "@/lib/static-cache-middleware";

Expand Down Expand Up @@ -68,7 +69,18 @@ export const Route = createFileRoute("/$")({
{ name: "twitter:description", content: pageDescription },
{ name: "twitter:image", content: DEFAULT_SOCIAL_IMAGE_URL },
],
links: [{ rel: "canonical", href: canonicalUrl }],
links: [
{ rel: "canonical", href: canonicalUrl },
...(loaderData
? [
{
rel: "alternate",
type: "text/markdown",
href: buildMarkdownAlternatePath(loaderData.url),
},
]
: []),
],
};
},
});
Expand Down Expand Up @@ -138,8 +150,8 @@ const clientLoader = browserCollections.docs.createClientLoader({
<DocsTitle>{frontmatter.title}</DocsTitle>
<DocsDescription>{frontmatter.description}</DocsDescription>
<div className="flex flex-row gap-2 items-center border-b -mt-4 pb-6">
<LLMCopyButton markdownUrl={`${url}.mdx`} />
<ViewOptions markdownUrl={`${url}.mdx`} githubUrl={githubUrl} />
<LLMCopyButton markdownUrl={buildMarkdownAlternatePath(url)} />
<ViewOptions markdownUrl={buildMarkdownAlternatePath(url)} githubUrl={githubUrl} />
</div>
<DocsBody>
<MDX components={getMDXComponents()} />
Expand Down
Loading
Loading