# 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/)）。
