feat: normalize config context and template API
Add FeedConfig and SitemapConfig structs to config.rs
with enabled: bool (default true) for opt-out control.
Refactor ConfigContext: flat nested_nav bool replaced
with nested nav: NavContext { nested, toc } to mirror
site.toml [nav] table structure.
Remove standalone base_url template variable; use
config.base_url as single source of truth with
trailing-slash trimming in ConfigContext::from().
Add section template fallback: try section/<type>.html,
fall back to section/default.html for unknown types.
Delete section/features.html (duplicate of default.html)
and homepage.html (dead code, never referenced).
Update base.html for new variable names.
This commit is contained in:
@@ -102,15 +102,15 @@ Items validated by codebase investigation:
|
||||
- [x] Change `tags` from `taxonomies.tags` nesting to flat `tags = ["..."]` (direct TOML array)
|
||||
- [x] Migrate all 17 content files from YAML (`---`) to TOML (`+++`) frontmatter
|
||||
- [x] Update embedded frontmatter examples in documentation pages (7 files)
|
||||
- [ ] Add `FeedConfig` and `SitemapConfig` structs to `config.rs` with `enabled: bool` (default `true`)
|
||||
- [ ] Wire feed/sitemap config into `SiteConfig` deserialization
|
||||
- [x] Add `FeedConfig` and `SitemapConfig` structs to `config.rs` with `enabled: bool` (default `true`)
|
||||
- [x] Wire feed/sitemap config into `SiteConfig` deserialization
|
||||
- [ ] Gate feed generation in `main.rs` on `config.feed.enabled`
|
||||
- [ ] Gate sitemap generation in `main.rs` on `config.sitemap.enabled`
|
||||
- [ ] Refactor `ConfigContext`: flat `nested_nav: bool` → nested `nav: NavContext { nested, toc }`
|
||||
- [ ] Remove duplicate `base_url` top-level template variable injection
|
||||
- [ ] Update `docs/templates/base.html`: `config.nested_nav` → `config.nav.nested`, `base_url` → `config.base_url`
|
||||
- [ ] Delete `docs/templates/section/features.html` and `docs/templates/homepage.html`
|
||||
- [ ] Add template section fallback in `render_section`: try `section/<type>.html`, fall back to `section/default.html`
|
||||
- [x] Refactor `ConfigContext`: flat `nested_nav: bool` → nested `nav: NavContext { nested, toc }`
|
||||
- [x] Remove duplicate `base_url` top-level template variable injection
|
||||
- [x] Update `docs/templates/base.html`: `config.nested_nav` → `config.nav.nested`, `base_url` → `config.base_url`
|
||||
- [x] Delete `docs/templates/section/features.html` and `docs/templates/homepage.html`
|
||||
- [x] Add template section fallback in `render_section`: try `section/<type>.html`, fall back to `section/default.html`
|
||||
- [x] Update/fix all existing tests to use TOML frontmatter
|
||||
- [ ] Add new tests: TOML parsing, date validation (valid + invalid), feed/sitemap config gating
|
||||
- [ ] Verify all 69 existing tests pass (updated for TOML)
|
||||
|
||||
4
docs/templates/base.html
vendored
4
docs/templates/base.html
vendored
@@ -7,7 +7,7 @@
|
||||
{% if page.description %}
|
||||
<meta name="description" content="{{ page.description }}" />{% endif %}
|
||||
<title>{{ title }} | {{ config.title }}</title>
|
||||
<link rel="canonical" href="{{ base_url }}{{ page_path }}" />
|
||||
<link rel="canonical" href="{{ config.base_url }}{{ page_path }}" />
|
||||
<link rel="stylesheet" href="{{ prefix }}/style.css" />
|
||||
<link rel="icon" type="image/png" href="{{ prefix }}/logo.png" />
|
||||
</head>
|
||||
@@ -39,7 +39,7 @@
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if config.nested_nav and item.children %}
|
||||
{% if config.nav.nested and item.children %}
|
||||
<div class="nav-children{% if is_current_section %} expanded{% endif %}">
|
||||
{% for child in item.children %}
|
||||
<a href="{{ prefix }}{{ child.path }}" {% if page_path==child.path %}class="active" {% endif %}>{{ child.label
|
||||
|
||||
9
docs/templates/homepage.html
vendored
9
docs/templates/homepage.html
vendored
@@ -1,9 +0,0 @@
|
||||
{% extends "base.html" %} {% block content %}
|
||||
<article class="homepage">
|
||||
<h1>{{ page.title }}</h1>
|
||||
{% if page.description %}
|
||||
<p class="lead">{{ page.description }}</p>
|
||||
{% endif %}
|
||||
<section class="content">{{ content | safe }}</section>
|
||||
</article>
|
||||
{% endblock content %}
|
||||
18
docs/templates/section/features.html
vendored
18
docs/templates/section/features.html
vendored
@@ -1,18 +0,0 @@
|
||||
{% extends "base.html" %} {% block content %}
|
||||
<article class="section-index">
|
||||
<h1>{{ section.title }}</h1>
|
||||
{% if section.description %}
|
||||
<p class="lead">{{ section.description }}</p>
|
||||
{% endif %}
|
||||
<nav class="section-nav">
|
||||
{% for item in items %}
|
||||
<a href="{{ prefix }}{{ item.path }}" class="section-link">
|
||||
<strong>{{ item.frontmatter.title }}</strong>
|
||||
{% if item.frontmatter.description %}
|
||||
<span>{{ item.frontmatter.description }}</span>
|
||||
{% endif %}
|
||||
</a>
|
||||
{% endfor %}
|
||||
</nav>
|
||||
</article>
|
||||
{% endblock content %}
|
||||
@@ -20,6 +20,40 @@ pub struct SiteConfig {
|
||||
/// Navigation configuration.
|
||||
#[serde(default)]
|
||||
pub nav: NavConfig,
|
||||
/// Feed (Atom) configuration.
|
||||
#[serde(default)]
|
||||
pub feed: FeedConfig,
|
||||
/// Sitemap configuration.
|
||||
#[serde(default)]
|
||||
pub sitemap: SitemapConfig,
|
||||
}
|
||||
|
||||
/// Feed (Atom) generation configuration.
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(default)]
|
||||
pub struct FeedConfig {
|
||||
/// Whether to generate an Atom feed (default: true).
|
||||
pub enabled: bool,
|
||||
}
|
||||
|
||||
impl Default for FeedConfig {
|
||||
fn default() -> Self {
|
||||
Self { enabled: true }
|
||||
}
|
||||
}
|
||||
|
||||
/// Sitemap generation configuration.
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(default)]
|
||||
pub struct SitemapConfig {
|
||||
/// Whether to generate a sitemap.xml (default: true).
|
||||
pub enabled: bool,
|
||||
}
|
||||
|
||||
impl Default for SitemapConfig {
|
||||
fn default() -> Self {
|
||||
Self { enabled: true }
|
||||
}
|
||||
}
|
||||
|
||||
/// Navigation configuration.
|
||||
|
||||
@@ -86,6 +86,9 @@ impl TemplateEngine {
|
||||
}
|
||||
|
||||
/// Render a section index page (blog index, projects index).
|
||||
///
|
||||
/// Tries `section/<type>.html` first, falls back to `section/default.html`
|
||||
/// if no type-specific template exists.
|
||||
pub fn render_section(
|
||||
&self,
|
||||
section: &Content,
|
||||
@@ -95,7 +98,12 @@ impl TemplateEngine {
|
||||
config: &SiteConfig,
|
||||
nav: &[NavItem],
|
||||
) -> Result<String> {
|
||||
let template = format!("section/{}.html", section_type);
|
||||
let preferred = format!("section/{}.html", section_type);
|
||||
let template = if self.tera.get_template_names().any(|n| n == preferred) {
|
||||
preferred
|
||||
} else {
|
||||
"section/default.html".to_string()
|
||||
};
|
||||
|
||||
let mut ctx = self.base_context(page_path, config, nav);
|
||||
ctx.insert("title", §ion.frontmatter.title);
|
||||
@@ -114,8 +122,6 @@ impl TemplateEngine {
|
||||
ctx.insert("nav", nav);
|
||||
ctx.insert("page_path", page_path);
|
||||
ctx.insert("prefix", &relative_prefix(page_path));
|
||||
// Trimmed base_url for canonical links
|
||||
ctx.insert("base_url", config.base_url.trim_end_matches('/'));
|
||||
ctx
|
||||
}
|
||||
}
|
||||
@@ -140,8 +146,17 @@ pub struct ConfigContext {
|
||||
pub title: String,
|
||||
pub author: String,
|
||||
pub base_url: String,
|
||||
/// Whether to display nested navigation
|
||||
pub nested_nav: bool,
|
||||
/// Navigation settings for templates.
|
||||
pub nav: NavContext,
|
||||
}
|
||||
|
||||
/// Navigation context for templates.
|
||||
#[derive(Serialize)]
|
||||
pub struct NavContext {
|
||||
/// Whether to display nested navigation.
|
||||
pub nested: bool,
|
||||
/// Whether table of contents is globally enabled.
|
||||
pub toc: bool,
|
||||
}
|
||||
|
||||
impl From<&SiteConfig> for ConfigContext {
|
||||
@@ -149,8 +164,11 @@ impl From<&SiteConfig> for ConfigContext {
|
||||
Self {
|
||||
title: config.title.clone(),
|
||||
author: config.author.clone(),
|
||||
base_url: config.base_url.clone(),
|
||||
nested_nav: config.nav.nested,
|
||||
base_url: config.base_url.trim_end_matches('/').to_string(),
|
||||
nav: NavContext {
|
||||
nested: config.nav.nested,
|
||||
toc: config.nav.toc,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -259,6 +277,8 @@ mod tests {
|
||||
nested: false,
|
||||
toc: true,
|
||||
},
|
||||
feed: crate::config::FeedConfig::default(),
|
||||
sitemap: crate::config::SitemapConfig::default(),
|
||||
};
|
||||
|
||||
let config_toc_false = SiteConfig {
|
||||
@@ -270,6 +290,8 @@ mod tests {
|
||||
nested: false,
|
||||
toc: false,
|
||||
},
|
||||
feed: crate::config::FeedConfig::default(),
|
||||
sitemap: crate::config::SitemapConfig::default(),
|
||||
};
|
||||
|
||||
// Frontmatter with explicit toc: true
|
||||
|
||||
Reference in New Issue
Block a user