Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
c59b664
feat: add copy page action for llms
LadyBluenotes Mar 31, 2026
7450ae0
feat: add copy page dropdown actions
LadyBluenotes Mar 31, 2026
793985a
feat: make copy page labels configurable
LadyBluenotes Mar 31, 2026
61fad14
feat: make llm page actions configurable
LadyBluenotes Mar 31, 2026
b950d6c
wip
LadyBluenotes Mar 31, 2026
0c3b22a
feat: implement copy page functionality with shared state for themes
LadyBluenotes Apr 1, 2026
b3462d1
feat: enhance copy page functionality with client readiness check and…
LadyBluenotes Apr 2, 2026
9590331
feat: adjust layout for improved responsiveness and prevent overflow
LadyBluenotes Apr 2, 2026
8d5eb24
feat: enhance copy functionality with navigation reset handling and l…
LadyBluenotes Apr 2, 2026
8ce5fdb
feat: normalize MDX nodes and enhance markdown conversion for directi…
LadyBluenotes Apr 2, 2026
d396300
feat: enhance markdown processing with directive container support an…
LadyBluenotes Apr 2, 2026
e22c5ca
feat: simplify plugin filtering by extracting document-only skipped l…
LadyBluenotes Apr 2, 2026
52811fe
feat: enhance markdown processing with tab group normalization and re…
LadyBluenotes Apr 2, 2026
0616bb9
refactor: rename llms.ts to page-markdown.ts and update imports; enha…
LadyBluenotes Apr 2, 2026
fb299df
refactor: simplify feedback handling in copy button; remove authored …
LadyBluenotes Apr 2, 2026
e9a5525
refactor: update type imports and remove unused options in code impor…
LadyBluenotes Apr 2, 2026
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
66 changes: 44 additions & 22 deletions docs/src/routes/guide/features/(3)llms.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -15,33 +15,35 @@ import { createSolidBase, defineTheme } from "@kobalte/solidbase/config";
import defaultTheme from "@kobalte/solidbase/default-theme";

const theme = defineTheme({
componentsPath: import.meta.resolve("./src/solidbase-theme"),
extends: defaultTheme,
componentsPath: import.meta.resolve("./src/solidbase-theme"),
extends: defaultTheme,
});

const solidBase = createSolidBase(theme);

export default {
...solidBase.startConfig({
ssr: true,
}),
plugins: [
solidBase.plugin({
title: "My Docs",
description: "Documentation for my project",
llms: true,
themeConfig: {
sidebar: {
"/": [
{
title: "Guide",
items: [{ title: "Getting Started", link: "/guide/getting-started" }],
},
],
},
},
}),
],
...solidBase.startConfig({
ssr: true,
}),
plugins: [
solidBase.plugin({
title: "My Docs",
description: "Documentation for my project",
llms: true,
themeConfig: {
sidebar: {
"/": [
{
title: "Guide",
items: [
{ title: "Getting Started", link: "/guide/getting-started" },
],
},
],
},
},
}),
],
};
```

Expand All @@ -50,6 +52,26 @@ Once enabled, the build emits:
- `llms.txt` at the site root
- one `.md` file per markdown route, such as `index.md` and `guide/getting-started.md`

## Default theme page actions

When you use the default theme, enabling `llms: true` also adds a page-level copy button that copies the generated markdown for the current page.

```ts title="app.config.ts"
solidBase.plugin({
llms: true,
});
```

The button uses the same generated `.md` output that powers `llms.txt`, so the copied content stays aligned with the markdown artifacts emitted by the build.

If you're building a custom theme, you can reuse the same behavior from the client API instead of copying the default theme implementation:

```ts title="theme.tsx"
import { useCopyPageMarkdown } from "@kobalte/solidbase/client";
```

The hook exposes copy state, in-flight state, and page eligibility so custom themes can provide their own UI while keeping the same LLMS-backed behavior.

## What goes into `llms.txt`

SolidBase builds the index from your docs metadata:
Expand Down
10 changes: 10 additions & 0 deletions src/client/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,16 @@
export { mdxComponents } from "virtual:solidbase/components";

Check failure on line 1 in src/client/index.tsx

View workflow job for this annotation

GitHub Actions / Biome

assist/source/organizeImports

The imports and exports are not sorted.
export { useRouteSolidBaseConfig } from "./config.js";
export { useSolidBaseContext } from "./context.jsx";
export {
type CopyPageState,
canCopyPageMarkdown,
clearPageMarkdownCache,
copyTextToClipboard,
getCurrentPageMarkdown,
getCurrentPageMarkdownPath,
toMarkdownPath,
useCopyPageMarkdown,
} from "./page-markdown.js";
export {
getLocale,
getLocaleLink,
Expand Down
2 changes: 1 addition & 1 deletion src/client/page-data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export interface BaseFrontmatter {
title?: string;
titleTemplate?: string;
description?: string;
llms?: { exclude?: boolean };
llms?: false | { exclude?: boolean };
}

interface CurrentPageData {
Expand Down
153 changes: 153 additions & 0 deletions src/client/page-markdown.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
import { useLocation } from "@solidjs/router";
import { createEffect, createSignal, onCleanup, onMount } from "solid-js";

import { useRouteSolidBaseConfig } from "./config.js";
import { type BaseFrontmatter, useCurrentPageData } from "./page-data.js";

const markdownCache = new Map<string, string>();
const BUTTON_RESET_MS = 2500;

export type CopyPageState = "idle" | "success" | "error";

function isLlmsDisabled(llms: BaseFrontmatter["llms"]) {
return llms === false || llms?.exclude === true;
}

export function canCopyPageMarkdown(
configLlms: boolean | undefined,
llms: BaseFrontmatter["llms"],
) {
return Boolean(configLlms) && !isLlmsDisabled(llms);
}

export function toMarkdownPath(pathname: string) {
if (pathname === "/") return "/index.md";

return `${pathname.replace(/\/$/, "")}.md`;
}

export function getCurrentPageMarkdownPath(
pathname = globalThis.location?.pathname,
) {
return pathname ? toMarkdownPath(pathname) : undefined;
}

export async function getCurrentPageMarkdown(
pathname = globalThis.location?.pathname,
fetchImpl = globalThis.fetch,
) {
const markdownPath = getCurrentPageMarkdownPath(pathname);

if (!markdownPath) {
throw new Error("Missing pathname");
}

const cached = markdownCache.get(markdownPath);
if (cached) {
return cached;
}

const response = await fetchImpl(markdownPath, {
headers: { Accept: "text/plain" },
});

if (!response.ok) {
throw new Error(`Failed to fetch markdown: ${response.status}`);
}

const content = await response.text();
markdownCache.set(markdownPath, content);
return content;
}

export async function copyTextToClipboard(
text: string,
clipboard = globalThis.navigator?.clipboard,
) {
if (!clipboard?.writeText) {
throw new Error("Clipboard API unavailable");
}

await clipboard.writeText(text);
}

export function clearPageMarkdownCache() {
markdownCache.clear();
}

export function useCopyPageMarkdown() {
const location = useLocation();
const pageData = useCurrentPageData();
const config = useRouteSolidBaseConfig<any>();
const [state, setState] = createSignal<CopyPageState>("idle");
const [isCopying, setIsCopying] = createSignal(false);
const [isClient, setIsClient] = createSignal(false);

let feedbackTimeout: ReturnType<typeof setTimeout> | undefined;

function clearFeedbackTimeout() {
if (!feedbackTimeout) return;
clearTimeout(feedbackTimeout);
feedbackTimeout = undefined;
}

function resetCopyFeedback() {
setState("idle");
setIsCopying(false);
clearFeedbackTimeout();
}

onMount(() => {
setIsClient(true);
});

onCleanup(() => {
clearFeedbackTimeout();
});

createEffect(() => {
location.pathname;
resetCopyFeedback();
});

const canCopy = () =>
canCopyPageMarkdown(config().llms, pageData()?.frontmatter.llms);
const isReady = () => isClient() && canCopy();

async function copy() {
if (!isReady() || isCopying()) return false;

const startedPathname = location.pathname;
setIsCopying(true);

try {
const markdown = await getCurrentPageMarkdown(startedPathname);
if (location.pathname !== startedPathname) return false;
await copyTextToClipboard(markdown);
if (location.pathname !== startedPathname) return false;
setState("success");
return true;
} catch (error) {
if (location.pathname !== startedPathname) return false;
console.error("[solidbase] Failed to copy page markdown", error);
setState("error");
return false;
} finally {
clearFeedbackTimeout();
if (location.pathname === startedPathname) {
feedbackTimeout = setTimeout(() => setState("idle"), BUTTON_RESET_MS);
} else {
clearFeedbackTimeout();
}
setIsCopying(false);
}
}

return {
canCopy,
copy,
isCopying,
isReady,
state,
};
}
Loading
Loading