# 多棵树

文档站常常分成几块，比如「指南」和「API 参考」：每块有自己的侧边栏，翻页也不会从一块翻到另一块。对同一个集合多次调用 `defineTree`，每棵树只放自己那部分页面就行。

## 定义

```ts
// src/lib/docs.ts
const guides = await defineTree(entries, ({ page, dir }) => [page('guides', dir('guides'))]);
const api = await defineTree(entries, ({ page, dir }) => [page('api', dir('api'))]);

export const sections = [
  { label: '指南', href: toHref('docs', 'guides'), tree: guides },
  { label: 'API 参考', href: toHref('docs', 'api'), tree: api },
];
```

`sections` 是你自己的数据，minidocs 不认识它。名字、入口页、要不要图标，都由你决定。

## 当前页面在哪棵树

用 `findPath` 找出包含当前页面的那棵树：

```ts
const section = sections.find((section) => findPath(section.tree, id).length > 0) ?? sections[0];
const nav = navState(section.tree, id);
const { prev, next } = neighbors(section.tree, id);
```

之后的侧边栏、翻页都用 `section.tree`，写法和只有一棵树时一样。

用 `findPath` 而不是 `id.startsWith('api')` 判断，这样树的结构只写在一个地方。

不在任何树里的页面，比如首页，会落到 `?? sections[0]`。你也可以改成不显示侧边栏。同一篇文档出现在两棵树里时，`find` 取 `sections` 里排在前面的那棵。

## 切换器

切换器是你自己写的 HTML，和语言切换器一样：

```astro
<nav aria-label="分区">
  {sections.map((item) => (
    <a href={item.href} aria-current={item === section ? 'true' : undefined}>
      {item.label}
    </a>
  ))}
</nav>
```

链接指向的是这一块的入口页，不一定是当前页面，所以 `aria-current` 用 `true` 而不是 `page`。

## 多语言

把 `sections` 写成按语言返回的函数，`t` 是你自己的界面文案：

```ts
export const sections = (locale: Locale) => [
  { label: t[locale].guides, href: toHref('docs', 'guides', locale), tree: guides[locale] },
  { label: t[locale].api, href: toHref('docs', 'api', locale), tree: api[locale] },
];
```

## llms.txt 和搜索

- llms.txt：每棵树各生成一份，或者把节点拼起来生成一份：`toLlmsTxt([...guides, ...api], { title })`。每棵树最上面那个带子节点的节点会成为一节，见 [llms.txt](/docs/guides/markdown.md#llmstxt)
- 搜索：把树的名字写进过滤条件，比如 ``data-pagefind-filter={`tree:${section.label}`}``，再放一组 `name="tree"` 的单选框，见[按分组过滤](/docs/guides/search.md#%E6%8C%89%E5%88%86%E7%BB%84%E8%BF%87%E6%BB%A4)

## 什么时候用多个集合

几棵树共用一个集合，所以也共用地址前缀和 frontmatter 的 schema。地址前缀不同、字段也不同的内容，比如放在 `/changelog/` 下、每篇都带日期的更新日志，应该是另一个集合，见[多个集合](/docs/guides/collections.md)。
