# Typst 模板的表单

为你自己的 Typst 函数配一个表单，这样撰写报告的人在可视化模式下看到的是带标签的字段，而不是函数调用语法。

Typst 模板通常自带一些函数：封面页、信息框、签署块。撰写报告的人会调用它们，例如 `#info-box(title: "Supply risk", tone: "risk")[...]`。如果你维护模板，可以把每个函数的参数描述一次。之后，可视化模式会把对该函数的每次调用显示为一张表单：带标签的字段、用于固定选项的列表、用于“是”或“否”的开关。编辑某个字段只会重写 `.typ` 文件中的那一个参数，所以报告仍是普通的 Typst 源码，编译方式也和原来一样。

## 读者会看到什么

单独占几行的调用会变成一张**卡片**。卡片的标题显示表单的名称，以及（如果你选择了的话）一段简短的摘要，例如信息框的标题。每个参数占一行，有它的标签和一个控件。

句子里的调用，例如 `#badge("review")`，会变成文字行中的一个**行内标签**。点击这个标签，会在一个小弹出框中打开同样的字段。

文本字段和标记字段在你按 Enter（多行字段中按 ⌘ Enter 或 Ctrl Enter）或离开该字段时保存。按 Esc 会恢复旧值。列表和开关会立即保存。每一次更改都是一个撤销步骤。

把光标移入某个调用时，会显示它的源码，与可视化模式中的其他元素一样。卡片上的**在源码中编辑**效果相同。

## 在哪里声明表单

有两个位置，格式相同：

- **在包中。** 在包的 `typst.toml` 中添加 `[tool.oleafly]` 部分。Typst 把 `[tool.*]` 部分留给其他工具，所以这个包仍能在各处安装和编译。这对 `@local` 包和来自 Typst Universe 的 `@preview` 包都适用。
- **在项目中。** 对于以普通 `.typ` 文件形式保存在项目内的模板，在项目根目录创建 `oleafly-forms.toml`。每个表单还要用 `module` 指出定义该函数的文件。

包中的表单：

```toml
[tool.oleafly]
schema = 1

[[tool.oleafly.forms]]
function = "info-box"
label = "Info box"
summary = "title"

  [[tool.oleafly.forms.fields]]
  arg = "title"
  kind = "text"
  label = "Heading"
  placeholder = "Short heading"
  required = true

  [[tool.oleafly.forms.fields]]
  arg = "tone"
  kind = "choice"
  label = "Tone"
  default = "note"
  options = [{ value = "note", label = "Note" }, { value = "risk", label = "Risk" }, "neutral"]

  [[tool.oleafly.forms.fields]]
  arg = 0
  kind = "content"
  label = "Text"
```

项目中的表单，写在 `oleafly-forms.toml` 中：

```toml
[tool.oleafly]
schema = 1

[[tool.oleafly.forms]]
module = "template/memo.typ"
function = "memo-header"
label = "Memo header"
fields = [
  { arg = "to", kind = "text", label = "To" },
  { arg = "subject", kind = "text", label = "Subject" },
]
```

## 键

`schema = 1` 是必需的。没有它，或者写了别的数字的文件，会被忽略。

每个 `[[tool.oleafly.forms]]` 条目有：

| 键 | 是否必需 | 含义 |
| --- | --- | --- |
| `function` | 是 | 函数的名称，一个 Typst 标识符。可以包含连字符。 |
| `module` | 仅在 `oleafly-forms.toml` 中 | 定义该函数的项目文件，相对于项目根目录，文件夹之间用 `/` 分隔。 |
| `label` | 否 | 表单在卡片和行内标签上的名称。默认是函数的名称。 |
| `summary` | 否 | 其值显示在名称旁边的参数，写法与字段的 `arg` 相同。 |
| `fields` | 是 | 一个或多个字段。 |

每个字段有：

| 键 | 是否必需 | 含义 |
| --- | --- | --- |
| `arg` | 是 | 命名参数的名称（`"title"`），或位置参数的数字（`0` 是第一个）。括号后面的 `[...]` 块算作位于括号内参数之后的位置参数。 |
| `kind` | 是 | `text`、`content`、`choice`、`number` 或 `toggle`。 |
| `label` | 是 | 字段的标签。 |
| `help` | 否 | 字段下方的简短说明。 |
| `placeholder` | 否 | 空字段中的灰色文字。 |
| `required` | 否 | `true` 会把空字段标记为缺失，并隐藏它的**清除**按钮。Oleafly 绝不会自己添加这个参数。 |
| `default` | 否 | 参数被省略时你的函数所用的值。它只会显示，绝不会写入。 |

各种类型及其额外的键：

- `text` 写入 Typst 字符串。加上 `multiline = true` 可得到更大的输入框。
- `content` 编辑 Typst 标记，并把它写成 `[...]` 块。字符串参数仍保持为字符串。
- `choice` 需要 `options`：一个字符串列表，或者在标签与值不同时使用 `{ value, label }` 表。值以字符串形式写入。
- `number` 接受 `integer`、`min`、`max` 和 `step`。数字没有单位。
- `toggle` 写入 `true` 或 `false`。

## 哪些调用会得到表单

Oleafly 会跟随报告自己在文件顶层的 `#import` 和 `#let` 行：

- `#import "@local/acme-report:0.1.0" as acme` 会让 `#acme.info-box(...)` 成为表单。
- `#import "@local/acme-report:0.1.0": info-box, status-badge as badge` 会让 `#info-box(...)` 和 `#badge(...)` 成为表单。
- `#import "@local/acme-report:0.1.0": *` 会让该包为其声明了表单的每个函数都成为表单。
- `#import "template/memo.typ": memo-header` 会匹配 `module` 为 `template/memo.typ` 的项目表单。路径从报告自己所在的文件夹读取，以 `/` 开头时则从项目根目录读取。

包的版本必须完全一致。文件中靠后位置的同名 `#let` 会从那一点起替换该表单。通过另一个重新导入该包的文件而到达的调用，保持显示其源码文本。

函数的名称如果与可视化模式已经绘制的 Typst 元素相同，例如 `table` 或 `figure`，用裸名称调用时会保持内置的外观。`#acme.table(...)` 或重命名的导入仍然会得到表单。

## 以只读方式显示的字段

有些参数无法安全地通过表单编辑。它们所在的行会显示源码和原因，**在源码中编辑**会跳到源码：

- **在源码中计算得出**：值是一个表达式，例如 `title: data.title`。
- **以其他类型的值书写**：例如在表单期望文本的地方写成了数字。
- **不在可选项之内**：你的 `options` 没有列出的值。
- **由展开参数设置**：带有 `..args` 的调用会传入表单看不到的值，所以每个字段都是只读的。

当某个字段无法添加到它应在的位置时，例如缺失参数之后的位置参数，表单会这样说明，并提供**在源码中添加**。

## 定义中的错误

在 Oleafly 中打开 `oleafly-forms.toml`，或打开复制进项目的包的 `typst.toml`，每个问题都会在它所在的行上显示为一个标记：未知的类型、缺少标签、重复的表单、位于项目之外的 `module`。有问题的表单会被略去，其他表单继续工作。大于 256 KB 的文件不会被读取。

## 共享模板

Oleafly 从编译器所用的同样那些文件夹中读取包的表单。没有 `@local` 包的协作者看到的是普通源码，也不会有错误。要让项目中的每个人都能用到这些表单，请打开**Typst 包**对话框，开启**使用项目文件夹中的包**，并选择**将包复制到项目**。这样，包及其表单就会随项目的 `typst-packages` 文件夹一起传递（参见 [Typst 项目](/docs/typst/)）。
