A singlemd resource is a single markdown page. One ctx.json plus one .md file per language. This is the workhorse type — every news post, every plain documentation page is a singlemd. Implementation: src/content/singlemd.rs.
Render one markdown body inside the standard page chrome (breadcrumb h1, navbar, footer). The body is compiled to HTML via pulldown_cmark with tables, smart-punctuation, and heading-attributes enabled (see fop::compile_markdown in src/content/fop.rs).
LangDict form — each language has its own markdown file:
{
"type": "singlemd",
"name": {
"en": "Reading the Docs",
"ja": "閲覧ガイド",
"zh": "阅读指南"
},
"desc": {
"en": "How to browse FDS Documents.",
"ja": "FDS 文書の閲覧方法。",
"zh": "如何浏览 FDS 文档。"
},
"file": {
"en": "user_en",
"ja": "user_ja",
"zh": "user_zh"
}
}
String form — every language reads the same .md (useful when content is not localised):
{
"type": "singlemd",
"name": { "en": "Release notes 2025.02.01" },
"file": "release_2025_02_01"
}
| Field | Type | Required | Notes |
|---|---|---|---|
type | string — must be "singlemd" | yes | Discriminator parsed in src/content.rs::render. |
name | LangDict (string or {en,ja,zh}) | yes | Page title. See Common: Header and LangDict. |
icon | string | optional | URL or path. Cleared on save when empty. |
desc | OptionLangDict | optional | Used as meta description and OG description. |
banner | OptionLangDict | optional | URL of an image rendered above the body via with_banner. |
file | string OR LangDict | yes | Filename stem (no .md) of the markdown body. |
file semantics:
<file>.md.<file[X]>.md. Missing language entries fall back to the default-language entry (LangDict::get behaviour).[A-Za-z0-9_-]+.<resource_folder>/
ctx.json
<file>.md ← string form
<file_en>.md ← LangDict form
<file_ja>.md
<file_zh>.md
Markdown is read by fop::read_markdown_file, which joins programfiles/content/ + resource folder + <slug>.md and compiles. Missing files render as an empty string (silently).
content.rs::render reads ctx.json, sees "singlemd", calls SingleMd::read(path).SingleMd::render resolves the active language via crate::lang_for(req).md_content(lang) reads <file_for_lang>.md and compiles it.<div class="container-func"><div class="markdown-content">…</div></div> and spliced into general.html by the page-level wrapper (banner, breadcrumb, SEO meta).?view=edit (see Route: edit) lets you:
SingleMd::rename).read_body / write_body). The body is written verbatim to <slug>.md via fop::write_text_file.icon, desc via the Header editor (see Common: Header and LangDict).The active language is the navbar dropdown’s current selection. To edit the Japanese body, switch to JA first, then enter edit mode.
Use ## and ### only — never #. The page’s <h1> is supplied by the breadcrumb’s last segment (see breadcrumb_split in src/content.rs). A stray # in the body produces a second h1 and tanks SEO.
singlemd supports ?view=download and returns the raw .md file for the active language with Content-Type: text/markdown; charset=UTF-8. See Route: download.
SingleMd::save():
ctx.json (preserves unknown keys).type to "singlemd".name, plus icon/desc/banner only when set).file field from the in-memory LangDict.fop::write_json_file.Notably save() does not touch the .md files — those are written by write_body.
.md file after renaming the file slug. read_markdown_file returns an empty body silently — the page will render blank instead of erroring. Always rename or create the file on disk alongside the JSON edit.# in the body. See “Markdown rule reminder” above.file from string to dict, ensure every language entry actually points to a real file. A missing language with no default-lang fallback renders blank.save() preserves unknown ctx keys — fine for forward compatibility, but means a typo’d key (fiel instead of file) lingers in the file forever. Inspect the saved JSON if something looks wrong.fop helpers prepend programfiles/content/. Never call std::fs::write on a resource-relative path directly.