An mdbook resource is an ordered set of markdown chapters that share one resource folder. One sidebar lists every chapter; the main pane renders the active one. Implementation: src/content/mdbook.rs.
Render a multi-chapter document with a sidebar of links. Used for governance documents (the Constitution), longer-form references, and anything where ordering matters and chapters share a parent.
{
"type": "mdbook",
"name": {
"en": "Charter",
"ja": "基本法",
"zh": "基本法"
},
"desc": {
"en": "FDS's founding charter.",
"ja": "FDSの設立基本法。",
"zh": "FDS 立组文件。"
},
"content": [
{
"name": "2025.07.15 版本",
"file": {
"en": "constitution_2025.07.15_en",
"ja": "constitution_2025.07.15_ja",
"zh": "constitution_2025.07.15"
}
},
{
"name": "2025.05.16 版本",
"file": "constitution_2025.05.16"
}
]
}
| Field | Type | Required | Notes |
|---|---|---|---|
type | string — must be "mdbook" | yes | Discriminator. |
name | LangDict | yes | Book title. See Common: Header and LangDict. |
icon | string | optional | URL or path. |
desc | OptionLangDict | optional | meta description / OG. |
banner | OptionLangDict | optional | Banner image URL. |
content | array of chapter objects | yes | See below. Empty array is legal but breaks the download endpoint. |
file | string | hidden / runtime | Not normally present in JSON. Populated at runtime from the URL’s trailing segment — see “Active chapter”. |
Each chapter object:
| Field | Type | Required | Notes |
|---|---|---|---|
name | string OR LangDict | yes | Sidebar label for this chapter. |
file | string OR LangDict | yes | Filename stem (no .md) of the chapter body. |
Order matters. Array order = sidebar order = download zip ordering.
The mdbook resource’s URL is its folder. When a visitor navigates to a chapter, the URL gains a trailing segment — e.g. /policies/constitution/constitution_2025.07.15. The dispatcher splits this into folder + file (via fop::split_path) and passes the file to MdBook::new as the file field.
MdBook::get_file(lang):
self.file when non-empty (a specific chapter was named in the URL).content[0].file[lang] when the URL hit the folder itself with no trailing segment.This is why you almost never set file in ctx.json — it’s a per-request runtime value.
<resource_folder>/
ctx.json
<chapter1_file>.md
<chapter2_file_en>.md
<chapter2_file_ja>.md
<chapter2_file_zh>.md
...
One .md per chapter per language. If a chapter’s file is a string, that single .md is shared across languages.
MdBook::render:
<div class="container-func"><div class="grid-container"><div class="links">.content, emit <p><a href="<chapter_file_for_lang>">{chapter_name}</a></p>. The href is a bare slug — it works because all chapters live in the same folder, so the browser resolves it as a sibling URL.<div class="markdown-content">, append the compiled markdown of the active chapter (via md_content(lang) → read_markdown_file), close all wrappers.content.rs::render wraps the result in the banner/breadcrumb/SEO chrome from general.html.?view=edit (see Route: edit) exposes:
rename).add_chapter(lang, name, file_slug) appends a chapter entry to content and creates an empty <file_slug>.md on disk if it doesn’t already exist.delete_chapter(idx) removes the entry. The .md file on disk is not deleted automatically — clean it up by hand if you want it gone.move_chapter(from, to).set_chapter_name(idx, lang, name).read_chapter_body(file_slug) / write_chapter_body(file_slug, body).Active language comes from the navbar dropdown.
?view=download returns a .zip of every chapter’s .md content for the active language. Filenames inside the zip are zero-padded with chapter index — 01-<slug>.md, 02-<slug>.md, … — so an unzip preserves reading order even when the slugs are not alphabetical. See Route: download for the full handler.
When content is empty, the download endpoint returns 404 ("No chapters to download").
MdBook::save():
ctx.json (preserves unknown keys).type = "mdbook".content from the in-memory Value — the entire chapter array is replaced.Notably the runtime file field is not written back — save() never persists the URL-derived active-chapter slug.
file slugs and .md filenames. Renaming a chapter’s file in JSON without renaming the file on disk leaves the chapter rendering blank..md files after delete_chapter. The handler doesn’t clean up. Decide deliberately whether to delete the file or keep it as a backup.01-, 02- prefixes shift with the new order.name / file as a bare string. Legal — every language sees the same value. Mix-and-match per chapter is fine (see the example).content. The resource renders an empty sidebar and an empty main pane. ?view=download 404s. Useful as a placeholder; otherwise add a chapter.file at the root level in ctx.json. The reader will pick it up and pin the active chapter to that slug regardless of URL. Almost never what you want — leave it out.