{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://assets.tiantianspeak.com/schemas/collection-article.schema.json",
  "title": "Collection article（文章）",
  "description": "一个文件 = 一篇文章（文件名建议 `*.article.json`）。正文在 `contents`（有序的内容块）；与 `contents` 同级的 `images` / `audios` / `videos` 存放媒体，`contents` 里的块用键引用它们。被 collection set 引用时整篇作为 set 里的一条 item（`text` 是各正文段落的纯文本用换行连起来）；也可以单独导入，得到只有一篇的 collection。",
  "type": "object",
  "required": [
    "type"
  ],
  "anyOf": [
    {
      "required": [
        "contents"
      ]
    },
    {
      "required": [
        "markdown"
      ]
    }
  ],
  "properties": {
    "type": {
      "const": "article",
      "description": "固定为 `article`，用来区别于 collection set。"
    },
    "id": {
      "type": "string",
      "pattern": "^(?!\\.)[^\\s/\\\\]{1,64}$",
      "maxLength": 64,
      "description": "文章 id；被 set 引用时作为条目 id。"
    },
    "hash": {
      "type": "string",
      "pattern": "^(?!\\.)[^\\s/\\\\]{1,64}$",
      "maxLength": 64,
      "description": "稳定标识：字母、数字、汉字和 `._-`，≤64 字符，不以 `.` 开头；不合格的值读取时当作没写。"
    },
    "description": {
      "type": "string",
      "description": "简介（也认 `简介` / `说明`）。"
    },
    "category": {
      "type": "string",
      "description": "一级分类，用于筛选（也认 `分类`）。分级标签请用 `tags`。"
    },
    "language": {
      "enum": [
        "zh",
        "en"
      ],
      "description": "文本语言。读取时也宽容 `en-US` / `english` / `英文` 等；发布的文件请写 `zh` 或 `en`。"
    },
    "tags": {
      "description": "分级标签。字符串数组（也可写成单个字符串）；每个标签是一条层级路径。",
      "oneOf": [
        {
          "type": "array",
          "items": {
            "type": "string",
            "minLength": 1,
            "pattern": "^[^/\\s][^/]*(/[^/]+)*$",
            "description": "分级标签：用 `/` 分隔的层级路径，如 `HSK/初级`、`名著/外国文学`。"
          },
          "uniqueItems": true
        },
        {
          "type": "string",
          "minLength": 1,
          "pattern": "^[^/\\s][^/]*(/[^/]+)*$",
          "description": "分级标签：用 `/` 分隔的层级路径，如 `HSK/初级`、`名著/外国文学`。"
        }
      ]
    },
    "name": {
      "type": "string",
      "description": "文章标题；不写时用文件名。"
    },
    "images": {
      "type": "object",
      "description": "图片表：键是给 `contents` 引用的名字，值是地址字符串，或带说明的对象 {src, …}。",
      "propertyNames": {
        "minLength": 1
      },
      "additionalProperties": {
        "oneOf": [
          {
            "type": "string",
            "minLength": 1,
            "description": "http(s) 网址，或相对路径（相对本 article 文件所在目录，不能是绝对路径、不能含 `..`）。相对路径在导入时按文件位置解析成完整网址（线上是下载网址，本地导入是 file 网址）。"
          },
          {
            "type": "object",
            "required": [
              "src"
            ],
            "properties": {
              "src": {
                "type": "string",
                "minLength": 1,
                "description": "http(s) 网址，或相对路径（相对本 article 文件所在目录，不能是绝对路径、不能含 `..`）。相对路径在导入时按文件位置解析成完整网址（线上是下载网址，本地导入是 file 网址）。"
              },
              "alt": {
                "type": "string",
                "description": "替代文字 / 标题（也认 `title`）。"
              },
              "credit": {
                "type": "string",
                "description": "版权 / 来源说明（也认 `source`）。"
              }
            },
            "additionalProperties": true
          }
        ]
      }
    },
    "audios": {
      "type": "object",
      "description": "音频表：键是给 `contents` 引用的名字，值是地址字符串，或带说明的对象 {src, …}。",
      "propertyNames": {
        "minLength": 1
      },
      "additionalProperties": {
        "oneOf": [
          {
            "type": "string",
            "minLength": 1,
            "description": "http(s) 网址，或相对路径（相对本 article 文件所在目录，不能是绝对路径、不能含 `..`）。相对路径在导入时按文件位置解析成完整网址（线上是下载网址，本地导入是 file 网址）。"
          },
          {
            "type": "object",
            "required": [
              "src"
            ],
            "properties": {
              "src": {
                "type": "string",
                "minLength": 1,
                "description": "http(s) 网址，或相对路径（相对本 article 文件所在目录，不能是绝对路径、不能含 `..`）。相对路径在导入时按文件位置解析成完整网址（线上是下载网址，本地导入是 file 网址）。"
              },
              "alt": {
                "type": "string",
                "description": "替代文字 / 标题（也认 `title`）。"
              },
              "credit": {
                "type": "string",
                "description": "版权 / 来源说明（也认 `source`）。"
              }
            },
            "additionalProperties": true
          }
        ]
      }
    },
    "videos": {
      "type": "object",
      "description": "视频表：键是给 `contents` 引用的名字，值是地址字符串，或带说明的对象 {src, …}。",
      "propertyNames": {
        "minLength": 1
      },
      "additionalProperties": {
        "oneOf": [
          {
            "type": "string",
            "minLength": 1,
            "description": "http(s) 网址，或相对路径（相对本 article 文件所在目录，不能是绝对路径、不能含 `..`）。相对路径在导入时按文件位置解析成完整网址（线上是下载网址，本地导入是 file 网址）。"
          },
          {
            "type": "object",
            "required": [
              "src"
            ],
            "properties": {
              "src": {
                "type": "string",
                "minLength": 1,
                "description": "http(s) 网址，或相对路径（相对本 article 文件所在目录，不能是绝对路径、不能含 `..`）。相对路径在导入时按文件位置解析成完整网址（线上是下载网址，本地导入是 file 网址）。"
              },
              "alt": {
                "type": "string",
                "description": "替代文字 / 标题（也认 `title`）。"
              },
              "credit": {
                "type": "string",
                "description": "版权 / 来源说明（也认 `source`）。"
              }
            },
            "additionalProperties": true
          }
        ]
      }
    },
    "markdown": {
      "type": "string",
      "minLength": 1,
      "description": "正文写成 Markdown（也认 `md`），没有 `contents` 时使用；导入时转成内容块：标题 → 小标题，段落 / 列表项 / 引用 → 正文段落（一项一段），独占一行的 `![说明](地址)` → 图片块（地址是 mp3 / m4a / wav / aac / ogg 为音频块，mp4 / mov / m4v / webm 为视频块，相对路径同媒体表规则）；行内 `**加粗**` `*斜体*` 保留，链接只留文字，代码块、分隔线、表格忽略。没写 `name` 时取第一个一级标题。"
    },
    "contents": {
      "type": "array",
      "minItems": 1,
      "description": "按顺序排列的内容块。可以直接写字符串（只有正文的段落）。至少一块有可朗读的正文段落。",
      "items": {
        "oneOf": [
          {
            "type": "string",
            "minLength": 1
          },
          {
            "title": "内容块",
            "type": "object",
            "description": "一个内容块：一段正文 / 一个小标题，和 / 或一个图片 / 音频 / 视频。至少要有 `text` 或一个媒体引用。",
            "properties": {
              "text": {
                "type": "string",
                "minLength": 1,
                "description": "正文，支持行内标记：`**加粗**`、`*斜体*`（`\\*` 表示字面的星号）；没成对的标记当普通字符。"
              },
              "style": {
                "enum": [
                  "paragraph",
                  "heading"
                ],
                "default": "paragraph",
                "description": "`heading` = 小标题（不朗读、不计入全文）；默认正文段落。"
              },
              "image": {
                "type": "string",
                "minLength": 1,
                "description": "引用 `images` 表里的键；键不存在时导入失败。"
              },
              "audio": {
                "type": "string",
                "minLength": 1,
                "description": "引用 `audios` 表里的键；键不存在时导入失败。"
              },
              "video": {
                "type": "string",
                "minLength": 1,
                "description": "引用 `videos` 表里的键；键不存在时导入失败。"
              },
              "pinyin": {
                "type": "string",
                "description": "每个汉字一个带调拼音，空格分隔，与 `text` 里的汉字一一对应；个数对不上时读取时整段拼音被忽略。"
              },
              "translation": {
                "type": "string",
                "description": "这一块的译文。"
              },
              "align": {
                "enum": [
                  "center",
                  "left"
                ],
                "description": "`center` 居中（适合诗词）；不写 = 左对齐。读取时也认 `居中`。"
              }
            },
            "anyOf": [
              {
                "required": [
                  "text"
                ]
              },
              {
                "required": [
                  "image"
                ]
              },
              {
                "required": [
                  "audio"
                ]
              },
              {
                "required": [
                  "video"
                ]
              }
            ],
            "additionalProperties": true
          }
        ]
      }
    }
  },
  "examples": [
    {
      "type": "article",
      "id": "wordroots-greek-pogonophobia",
      "name": "Are You Scared of Santa and His Beard?",
      "language": "en",
      "category": "Word Roots",
      "tags": [
        "Word Roots/Greek"
      ],
      "images": {
        "photo": {
          "src": "images/pogonophobia.jpg",
          "alt": "Santa and a child",
          "credit": "Pratoomrat Tongkate | Adobe Stock"
        }
      },
      "audios": {
        "read": "https://assets.tiantianspeak.com/audio/pogonophobia.mp3"
      },
      "contents": [
        {
          "image": "photo"
        },
        {
          "audio": "read"
        },
        "If you see a big bushy beard, you might have **pogonophobia**.",
        {
          "text": "Roots",
          "style": "heading"
        },
        "*pogon* means beard."
      ]
    }
  ]
}
