Skip to content
Open
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
6 changes: 1 addition & 5 deletions dev/vite.config.mts
Original file line number Diff line number Diff line change
Expand Up @@ -155,11 +155,7 @@ export default defineConfig({
"/": createDevSidebar("./src/routes", ["es", "fr", "router", "v1"]),
},
search: {
docsearch: {
appId: "QAS0JNC31U",
indexName: "SolidBase Docs",
apiKey: "768424fdd93c7150a7a017eb556fa8a3",
},
local: true,
},
},
}),
Expand Down
14 changes: 14 additions & 0 deletions docs/src/routes/guide/(2)config.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -445,3 +445,17 @@ sidebar: {
},
// ..
```

#### Search

Enable built-in local search without an external service:

```ts title="app.config.ts"
// ..
search: {
local: true,
},
// ..
```

See the [Search reference](/reference/default-theme/search) for page exclusion, matching behavior, text overrides, and Algolia DocSearch configuration.
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,6 @@ Top navigation, version controls, locale controls, theme controls, and mobile na
- uses a mobile nav dialog on small screens
- includes [`VersionSelector`](/reference/default-theme/components/version-selector) next to the logo when multiple versions are available
- includes [`LocaleSelector`](/reference/default-theme/components/locale-selector) and [`ThemeSelector`](/reference/default-theme/components/theme-selector)
- includes [local search or DocSearch](/reference/default-theme/search) when configured
- shows mobile sidebar and table-of-contents toggles only when content exists
- marks nav links active using `activeMatch` or the item `link`
2 changes: 1 addition & 1 deletion docs/src/routes/reference/default-theme/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ Configuration lives in:
- `badges`: `{ icons?: Record<string, string | Component | { svg: string } | { component: Component }> }`
- `nav`: `Array<{ text: string; link: string; activeMatch?: string }>`
- `sidebar`: `SidebarConfig`
- `search`: `object`
- `search`: [`SearchConfig`](/reference/default-theme/search)
- `fonts`: `{ inter?: false; lexend?: false; jetbrainsMono?: false } | false`
- `text`: `Partial<DefaultThemeTextConfig>`

Expand Down
70 changes: 70 additions & 0 deletions docs/src/routes/reference/default-theme/search.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
---
title: Search
---

# Search

The default theme supports built-in local search and hosted Algolia DocSearch.

## Local Search

Enable local search without an external service:

```ts title="app.config.ts"
themeConfig: {
search: {
local: true,
},
},
```

SolidBase uses [Pagefind](https://pagefind.app/) to build the search index from page titles, descriptions, headings, and Markdown content. Results are limited to the current locale and configured route axes, such as project and version.

Pagefind loads its search engine and only the required index chunks when search starts. It supports prefix matching and language-aware word stemming. Use `Command+K` or `Control+K` to open the dialog.

Exclude a page from local search with frontmatter:

```md
---
search: false
---
```

## Algolia DocSearch

Configure hosted DocSearch instead of local search:

```ts title="app.config.ts"
themeConfig: {
search: {
docsearch: {
appId: "YOUR_APP_ID",
apiKey: "YOUR_SEARCH_API_KEY",
indexName: "YOUR_INDEX_NAME",
},
},
},
```

Local search and DocSearch are mutually exclusive.

## Content Security Policy

Pagefind uses WebAssembly and a web worker. A strict Content Security Policy must allow `script-src 'wasm-unsafe-eval'` and `worker-src 'self' blob:`. See [Pagefind's hosting guide](https://pagefind.app/docs/hosting/) for details.

## Text

Override search labels through `themeConfig.text`:

```ts title="app.config.ts"
themeConfig: {
text: {
search: "Search",
searchPlaceholder: "Search documentation",
searchClose: "Close search",
searchLoading: "Loading search…",
searchNoResults: "No results found",
searchUnavailable: "Search is unavailable",
},
},
```
15 changes: 15 additions & 0 deletions docs/src/routes/reference/frontmatter.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -170,3 +170,18 @@ sitemap:
```

You can also write `sitemap: false` to exclude the page entirely.

## Search

Type: `false | undefined`

Controls whether the page is included in the built-in local search index.

```md
---
title: Internal Notes
search: false
---
```

See the [Search reference](/reference/default-theme/search) for local search and DocSearch configuration.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,7 @@
"mdast-util-mdx-jsx": "^3.2.0",
"mdast-util-to-string": "^4.0.0",
"mdast-util-toc": "^7.1.0",
"pagefind": "1.5.2",
"parse-numeric-range": "^1.3.0",
"prettier": "4.0.0-alpha.13",
"rehype-autolink-headings": "^7.1.0",
Expand Down
73 changes: 73 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

25 changes: 22 additions & 3 deletions src/config/vite-plugin/generated-asset.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ type GeneratedAssetPluginOptions = {
source: string,
importer: string,
) => Promise<{ id: string } | null>,
watch: (filePath: string) => void,
): Promise<void>;
};

Expand All @@ -26,6 +27,21 @@ export function createGeneratedAssetPlugin(
): PluginOption {
let root = process.cwd();
let assetRoot = join(root, options.assetDir);
let watchedFiles = new Set<string>();

async function writeAssets(context: any) {
const nextWatchedFiles = new Set<string>();
await options.write(
root,
(source, importer) => context.resolve(source, importer),
(filePath) => {
const normalizedPath = normalize(filePath);
nextWatchedFiles.add(normalizedPath);
context.addWatchFile(normalizedPath);
},
);
watchedFiles = nextWatchedFiles;
}

async function serveGeneratedAsset(url: string | undefined, res: any) {
if (!url || url === "/") return false;
Expand Down Expand Up @@ -55,6 +71,8 @@ export function createGeneratedAssetPlugin(
res.setHeader("Content-Disposition", "inline");
} else if (filePath.endsWith(".txt")) {
res.setHeader("Content-Type", "text/plain; charset=utf-8");
} else if (filePath.endsWith(".js") || filePath.endsWith(".mjs")) {
res.setHeader("Content-Type", "text/javascript; charset=utf-8");
}
res.statusCode = 200;
res.end(content);
Expand Down Expand Up @@ -94,9 +112,10 @@ export function createGeneratedAssetPlugin(
});
},
async buildStart() {
await options.write(root, (source: string, importer: string) =>
this.resolve(source, importer),
);
await writeAssets(this);
},
async watchChange(id) {
if (watchedFiles.has(normalize(id))) await writeAssets(this);
},
};
}
31 changes: 22 additions & 9 deletions src/default-theme/components/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,19 @@ import {
} from "../context.jsx";
import { useRouteConfig } from "../utils.js";
import styles from "./Header.module.css";
import DocSearch from "./vendor/DocSearch.jsx";

export default function Header() {
const [tocRef, setTocRef] = createSignal<HTMLElement>();
const [navRef, setNavRef] = createSignal<HTMLElement>();

const { ThemeSelector, LocaleSelector, VersionSelector, TableOfContents } =
useDefaultThemeComponents();
const {
DocSearch,
LocalSearch,
ThemeSelector,
LocaleSelector,
VersionSelector,
TableOfContents,
} = useDefaultThemeComponents();

const {
tocOpen,
Expand All @@ -47,6 +52,18 @@ export default function Header() {
sidebar()!.items.length > 0;
const hasToc = () =>
frontmatter()?.toc !== false && tocContent() && tocContent()!.length > 0;
const SearchControl = (props: { shortcut?: boolean }) => (
<Show
when={config().themeConfig?.search?.local}
fallback={
<Show when={config().themeConfig?.search?.docsearch}>
{(docsearch) => <DocSearch docsearch={docsearch()} />}
</Show>
}
>
<LocalSearch shortcut={props.shortcut} />
</Show>
);

return (
<header class={styles.header}>
Expand Down Expand Up @@ -109,18 +126,14 @@ export default function Header() {
)}
</Show>
<div class={styles["nav-popup-selectors"]}>
<Show when={config().themeConfig?.search?.docsearch}>
{(docsearch) => <DocSearch docsearch={docsearch()} />}
</Show>
<SearchControl />
<LocaleSelector />
<ThemeSelector />
</div>
</Dialog.Content>
</Dialog.Portal>
</Dialog>
<Show when={config().themeConfig?.search?.docsearch}>
{(docsearch) => <DocSearch docsearch={docsearch()} />}
</Show>
<SearchControl shortcut />
<Show when={config().themeConfig?.nav}>
{(nav) => (
<For each={nav()}>
Expand Down
Loading
Loading