# 文档树

集合里的页面没有顺序。文档树决定它们怎样组织：侧边栏按它渲染，上一页和下一页按它的顺序计算。

## 定义

```ts
// src/lib/docs.ts
import { getCollection } from 'astro:content';
import { defineTree } from '@minidocs/astro/runtime';

const entries = await getCollection('docs');

export const tree = await defineTree(entries, ({ page, group, dir, link }) => [
  page('index'),
  page('install'),
  group('指南', dir('guides')),
  link('GitHub', 'https://github.com/example/repo'),
]);
```

`defineTree` 的回调返回一组节点。节点是文档树里的一项，比如一个页面或一个分组，这一组节点就是侧边栏的第一层。

## 四个函数

| 函数                        | 产出                         |
| ------------------------- | -------------------------- |
| `page(id, children?)`     | 一个页面，可以带子节点                |
| `group(label, children)`  | 一个没有自己页面的分组                |
| `dir(path, { compare? })` | 目录里的页面和子目录，不含目录自己的 `index` |
| `link(label, href)`       | 一个外部链接                     |

`dir` 不含目录自己的 `index`，所以一个目录可以有两种写法：

```ts
group('指南', dir('guides')); // 分组只是一个标题
page('guides', dir('guides')); // 分组本身也是页面，就是 guides/index.mdx
```

`dir` 遇到子目录时也是这样：有 `index` 的子目录成为带子节点的页面，没有的成为分组，标题是目录名。

## 节点

渲染时你拿到的每个节点都有这几个字段：

| 字段         | 含义                                           |
| ---------- | -------------------------------------------- |
| `label`    | 显示的文字：页面取 frontmatter 的 `title`，分组取你给的标题或目录名 |
| `children` | 子节点，没有时是空数组                                  |
| `page`     | 页面节点才有，`page.entry` 是集合里的原始条目                |
| `href`     | 外部链接才有                                       |

怎样把它渲染成侧边栏，见[侧边栏](/docs/guides/sidebar.md)。

## 排序

`dir` 默认按文件名排序。想按别的方式排，传一个 `compare`，比如按 frontmatter 里的 `order`：

```ts
import { type DocNode } from '@minidocs/astro/runtime';

type Entry = (typeof entries)[number];

const byOrder = (a: DocNode<Entry>, b: DocNode<Entry>) =>
  (a.page?.entry.data.order ?? Infinity) - (b.page?.entry.data.order ?? Infinity);

group('指南', dir('guides', { compare: byOrder }));
```

`compare` 对 `dir` 列出的每一层都生效。`order` 是你自己在 schema 里声明的字段；分组节点没有 `page`，上面的写法会把它们排在最后。

## 多语言站点

回调对每种语言各执行一次，`tree` 是按语言索引的对象，渲染时用 `tree[locale]`。回调参数里的 `locale` 可以用来翻译分组的标题：

```ts
export const tree = await defineTree(entries, ({ locale, page, group, dir }) => [
  page('index'),
  group(locale === 'zh' ? '指南' : 'Guides', dir('guides')),
]);
```

## 规则

- 文档树只决定导航，不影响地址。调整顺序或者把页面挪进别的分组，地址都不会变
- 同一个页面可以在树里出现多次，但只有第一次出现的位置算作当前位置，翻页也按它计算
