自定义组件
自定义组件把几个内置组件拼在一起,JSON 里像内置组件一样引用。渲染前 fmtagno 先把它展开成内置组件,所以三种格式都能用。
自定义组件只能用内置组件和别的自定义组件拼,不能写代码。
定义一个组件
Section titled “定义一个组件”一个文件一个组件,用 JSON 写:
{ "name": "Callout", "description": "带标题的提示框,子元素放在标题下面", "container": true, "props": { "title": { "type": "inline", "required": true, "description": "提示框标题" }, "tone": { "type": "string", "default": "#1a73e8", "description": "标题颜色" } }, "template": { "root": "box", "elements": { "box": { "type": "Document", "children": ["head", "$children"] }, "head": { "type": "Text", "props": { "text": "{{title}}" }, "style": { "bold": true, "color": "{{tone}}", "background": "#F5F7FA", "padding": 6 } } } }}用的时候:
"tip": { "type": "Callout", "props": { "title": "提示" }, "children": ["tip-body"] },"tip-body": { "type": "Text", "props": { "text": "组件定义放在文档旁边的 catalog/ 目录里。" } }定义文件的字段
Section titled “定义文件的字段”| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
name |
字符串 | 是 | 组件名。大写字母开头,只能用字母、数字、_,不能和内置组件重名 |
description |
字符串 | 否 | 说明,fmtagno catalog 会显示 |
container |
布尔 | 否 | 能不能放子元素,默认 false |
props |
对象 | 否 | 参数定义,键是参数名 |
template |
对象 | 是 | 模板:root 是根元素 id,elements 和文档的 elements 写法一样 |
每个参数:
| 字段 | 说明 |
|---|---|
type |
string(默认)、number、integer、boolean、array、object、inline(字符串或 runs)、any |
required |
必填,默认 false |
default |
默认值,类型要和 type 对得上 |
description |
说明,会写进 catalog --json 的 schema |
- 整个替换:字符串正好是
"{{title}}"时,整个换成参数的值。数组、对象也行,比如 runs、表格的rows。 - 文字替换:夹在文字里时按文字替换,比如
"第 {{n}} 章:{{title}}"。这时参数只能是字符串、数字或布尔。 - 没给值、也没默认值的可选参数,换成空。
- 模板里引用了没定义的参数,报错。
- 插槽:容器组件要在模板某个元素的
children里写一次"$children",实例的子元素放到那里。不是容器就不能写。 - 模板元素的 id 不能带点号。
- 模板里可以用别的自定义组件,最多套 32 层,超过就当成环报错。
展开后的 id
Section titled “展开后的 id”- 实例的 id 留给模板的根元素。上面的例子里,
tip展开后就是那个Document。 - 模板里其他元素的 id 变成
实例id.模板id,比如tip.head。报错信息里会看到这种 id。
这些都会报错:
- 少写必填参数。
- 写了没定义的参数。
- 参数类型不对。
- 不是容器却写了
children。 - 组件定义了不止一次。
三个地方,都能用:
- 文档旁边的
catalog/目录,自动读。 --catalog指定的文件或目录,可以写多次。render、check、catalog三个命令都认。- 文档里的
components数组,适合一次传完整份文档(比如 AI 生成时)。
目录里只读 .json 文件,不进子目录。
{ "components": [ { "name": "Signature", "description": "落款:右对齐的署名和日期", "props": { "who": { "type": "string", "required": true }, "date": { "type": "string" } }, "template": { "root": "s", "elements": { "s": { "type": "Text", "props": { "text": "{{who}} {{date}}" }, "style": { "align": "right", "color": "#666666" } } } } } ]}fmtagno catalog 不读文档旁边的 catalog/,要看自定义组件就加 --catalog:
fmtagno catalog --catalog catalog/fmtagno catalog --json --catalog catalog/-
模板里写的
style、parts是组件的默认外观。 -
它们排在通用组件样式后面。顶层
styles里写"Text": {...},改不动 Callout 里的标题。 -
要改组件里面,用指名的键:
键 管什么 Callout展开后的根元素 Callout:head模板里 id 为 head的元素PriceTable:t.header模板元素 t的部位header -
实例上写的
style、parts优先级最高。
叠加的完整顺序见样式。
{ "version": "0.1", "meta": { "title": "自定义组件示例", "lang": "zh-CN" }, "components": [ { "name": "Chapter", "description": "章节标题加一段导语", "props": { "n": { "type": "integer", "required": true }, "title": { "type": "string", "required": true }, "lead": { "type": "inline", "default": "" } }, "template": { "root": "h", "elements": { "h": { "type": "Document", "children": ["title", "lead"] }, "title": { "type": "Heading", "props": { "level": 2, "text": "第 {{n}} 章:{{title}}" } }, "lead": { "type": "Text", "props": { "text": "{{lead}}" }, "style": { "color": "#666666" } } } } }, { "name": "Callout", "description": "带标题的提示框", "container": true, "props": { "title": { "type": "inline", "required": true }, "tone": { "type": "string", "default": "#1a73e8" } }, "template": { "root": "box", "elements": { "box": { "type": "Document", "children": ["head", "$children"] }, "head": { "type": "Text", "props": { "text": "{{title}}" }, "style": { "bold": true, "color": "{{tone}}", "background": "#F5F7FA", "padding": 6 } } } } } ], "styles": { "Callout:head": { "fontSize": 12 } }, "root": "doc", "elements": { "doc": { "type": "Document", "children": ["ch1", "tip"] }, "ch1": { "type": "Chapter", "props": { "n": 1, "title": "为什么要自定义组件", "lead": ["把常用的排版", { "text": "组合起来", "bold": true }, ",JSON 里一行引用。"] } }, "tip": { "type": "Callout", "props": { "title": "提示", "tone": "#CF222E" }, "children": ["tip-body"] }, "tip-body": { "type": "Text", "props": { "text": "定义可以放在 catalog/ 目录,也可以像这样直接写进文档。" } } }}