From 3df7fda26afd059d1cbba4a5d6c203e5c3537cb5 Mon Sep 17 00:00:00 2001 From: Timothy DeHerrera Date: Sat, 31 Jan 2026 14:59:49 -0700 Subject: [PATCH] feat(templates): add Tera runtime template engine Lay groundwork for user-editable templates by adding Tera as a runtime template engine alongside the existing maud templates. Changes: - Add tera dependency - Create TemplateEngine struct with render methods - Add TemplateLoad/TemplateRender error variants - Add section_type/template fields to Frontmatter - Create templates/ directory with base, page, section, and content templates Dead code warnings are expected; TemplateEngine will be wired in to replace maud in subsequent commits. --- Cargo.lock | 283 ++++++++++++++++++++++++++++++++ Cargo.toml | 1 + src/content.rs | 11 +- src/error.rs | 8 + src/main.rs | 1 + src/template_engine.rs | 199 ++++++++++++++++++++++ templates/base.html | 25 +++ templates/content/default.html | 13 ++ templates/content/post.html | 25 +++ templates/homepage.html | 13 ++ templates/page.html | 10 ++ templates/section/blog.html | 20 +++ templates/section/default.html | 15 ++ templates/section/projects.html | 24 +++ 14 files changed, 647 insertions(+), 1 deletion(-) create mode 100644 src/template_engine.rs create mode 100644 templates/base.html create mode 100644 templates/content/default.html create mode 100644 templates/content/post.html create mode 100644 templates/homepage.html create mode 100644 templates/page.html create mode 100644 templates/section/blog.html create mode 100644 templates/section/default.html create mode 100644 templates/section/projects.html diff --git a/Cargo.lock b/Cargo.lock index 08a8c59..6c23ac8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -41,6 +41,15 @@ version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + [[package]] name = "anyhow" version = "1.0.100" @@ -53,6 +62,12 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7d902e3d592a523def97af8f317b08ce16b7ab854c1985a0c671e6f15cebc236" +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + [[package]] name = "base64-simd" version = "0.7.0" @@ -114,6 +129,16 @@ dependencies = [ "syn 2.0.114", ] +[[package]] +name = "bstr" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63044e1ae8e69f3b5a92c736ca6269b8d12fa7efe39bf34ddb06d102cf0e2cab" +dependencies = [ + "memchr", + "serde", +] + [[package]] name = "bumpalo" version = "3.19.1" @@ -164,6 +189,39 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" +[[package]] +name = "chrono" +version = "0.4.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fac4744fb15ae8337dc853fee7fb3f4e48c0fbaa23d0afe49c447b4fab126118" +dependencies = [ + "iana-time-zone", + "num-traits", + "windows-link", +] + +[[package]] +name = "chrono-tz" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93698b29de5e97ad0ae26447b344c482a7284c737d9ddc5f9e52b74a336671bb" +dependencies = [ + "chrono", + "chrono-tz-build", + "phf 0.11.3", +] + +[[package]] +name = "chrono-tz-build" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c088aee841df9c3041febbb73934cfc39708749bf96dc827e3359cd39ef11b1" +dependencies = [ + "parse-zoneinfo", + "phf 0.11.3", + "phf_codegen 0.11.3", +] + [[package]] name = "const-str" version = "0.3.2" @@ -193,6 +251,12 @@ dependencies = [ "unicode-segmentation", ] +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + [[package]] name = "cpufeatures" version = "0.2.17" @@ -339,6 +403,12 @@ dependencies = [ "matches", ] +[[package]] +name = "deunicode" +version = "1.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abd57806937c9cc163efc8ea3910e00a62e2aeb0b8119f1793a978088f8f6b04" + [[package]] name = "digest" version = "0.10.7" @@ -455,6 +525,30 @@ dependencies = [ "wasip2", ] +[[package]] +name = "globset" +version = "0.4.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52dfc19153a48bde0cbd630453615c8151bce3a5adfac7a0aebfbf0a1e1f57e3" +dependencies = [ + "aho-corasick", + "bstr", + "log", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "globwalk" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf760ebf69878d9fd8f110c89703d90ce35095324d1f1edcb595c63945ee757" +dependencies = [ + "bitflags", + "ignore", + "walkdir", +] + [[package]] name = "graphlib_rust" version = "0.0.2" @@ -516,12 +610,61 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +[[package]] +name = "humansize" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6cb51c9a029ddc91b07a787f1d86b53ccfa49b0e86688c946ebe8d3555685dd7" +dependencies = [ + "libm", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + [[package]] name = "ident_case" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" +[[package]] +name = "ignore" +version = "0.4.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3d782a365a015e0f5c04902246139249abf769125006fbe7649e2ee88169b4a" +dependencies = [ + "crossbeam-deque", + "globset", + "log", + "memchr", + "regex-automata", + "same-file", + "walkdir", + "winapi-util", +] + [[package]] name = "indexmap" version = "2.13.0" @@ -600,6 +743,12 @@ version = "0.2.180" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc" +[[package]] +name = "libm" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" + [[package]] name = "lightningcss" version = "1.0.0-alpha.70" @@ -724,6 +873,7 @@ dependencies = [ "pulldown-cmark", "serde", "tempfile", + "tera", "thiserror 2.0.18", "toml 0.8.23", "tree-sitter", @@ -743,6 +893,15 @@ dependencies = [ "walkdir", ] +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + [[package]] name = "once_cell" version = "1.21.3" @@ -804,6 +963,15 @@ dependencies = [ "windows-link", ] +[[package]] +name = "parse-zoneinfo" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f2a05b18d44e2957b88f96ba460715e295bc1d7510468a2f3d3b44535d26c24" +dependencies = [ + "regex", +] + [[package]] name = "pastey" version = "0.1.1" @@ -816,6 +984,12 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3" +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + [[package]] name = "pest" version = "2.8.5" @@ -964,6 +1138,15 @@ dependencies = [ "siphasher", ] +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + [[package]] name = "precomputed-hash" version = "0.1.1" @@ -1078,6 +1261,18 @@ version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", "rand_core", ] @@ -1086,6 +1281,9 @@ name = "rand_core" version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.17", +] [[package]] name = "rapidhash" @@ -1338,6 +1536,16 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" +[[package]] +name = "slug" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "882a80f72ee45de3cc9a5afeb2da0331d58df69e4e7d8eeb5d3c7784ae67e724" +dependencies = [ + "deunicode", + "wasm-bindgen", +] + [[package]] name = "smallvec" version = "1.15.1" @@ -1418,6 +1626,28 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "tera" +version = "1.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8004bca281f2d32df3bacd59bc67b312cb4c70cea46cbd79dbe8ac5ed206722" +dependencies = [ + "chrono", + "chrono-tz", + "globwalk", + "humansize", + "lazy_static", + "percent-encoding", + "pest", + "pest_derive", + "rand", + "regex", + "serde", + "serde_json", + "slug", + "unicode-segmentation", +] + [[package]] name = "thiserror" version = "1.0.69" @@ -1821,12 +2051,65 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + [[package]] name = "windows-link" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link", +] + [[package]] name = "windows-sys" version = "0.61.2" diff --git a/Cargo.toml b/Cargo.toml index 8aed6b1..abee6d6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,6 +34,7 @@ lightningcss = "1.0.0-alpha.70" # Config parsing katex-rs = "0.2.3" serde = { version = "1", features = ["derive"] } +tera = "1" toml = "0.8" # Diagram rendering diff --git a/src/content.rs b/src/content.rs index 203cf7e..12de7b5 100644 --- a/src/content.rs +++ b/src/content.rs @@ -2,6 +2,7 @@ use crate::error::{Error, Result}; use gray_matter::{engine::YAML, Matter}; +use serde::Serialize; use std::fs; use std::path::{Path, PathBuf}; @@ -19,7 +20,7 @@ pub enum ContentKind { } /// A navigation menu item discovered from the filesystem. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Serialize)] pub struct NavItem { /// Display label (from nav_label or title) pub label: String, @@ -42,6 +43,10 @@ pub struct Frontmatter { pub link_to: Option, /// Custom navigation label (defaults to title) pub nav_label: Option, + /// Section type for template dispatch (e.g., "blog", "projects") + pub section_type: Option, + /// Override template for this content item + pub template: Option, } /// A content item ready for rendering. @@ -134,6 +139,8 @@ fn parse_frontmatter(path: &Path, parsed: &gray_matter::ParsedEntity) -> Result< let weight = pod.get("weight").and_then(|v| v.as_i64().ok()); let link_to = pod.get("link_to").and_then(|v| v.as_string().ok()); let nav_label = pod.get("nav_label").and_then(|v| v.as_string().ok()); + let section_type = pod.get("section_type").and_then(|v| v.as_string().ok()); + let template = pod.get("template").and_then(|v| v.as_string().ok()); // Handle nested taxonomies.tags structure let tags = if let Some(taxonomies) = pod.get("taxonomies") { @@ -162,6 +169,8 @@ fn parse_frontmatter(path: &Path, parsed: &gray_matter::ParsedEntity) -> Result< weight, link_to, nav_label, + section_type, + template, }) } diff --git a/src/error.rs b/src/error.rs index 02ee233..c1c6537 100644 --- a/src/error.rs +++ b/src/error.rs @@ -40,6 +40,14 @@ pub enum Error { /// Failed to parse configuration file. #[error("invalid config in {path}: {message}")] Config { path: PathBuf, message: String }, + + /// Failed to load templates. + #[error("failed to load templates: {message}")] + TemplateLoad { message: String }, + + /// Failed to render template. + #[error("failed to render template '{template}': {message}")] + TemplateRender { template: String, message: String }, } /// Result type alias for compiler operations. diff --git a/src/main.rs b/src/main.rs index 1400e3b..6d4e265 100644 --- a/src/main.rs +++ b/src/main.rs @@ -11,6 +11,7 @@ mod highlight; mod math; mod mermaid; mod render; +mod template_engine; mod templates; use crate::content::{discover_nav, Content, ContentKind, NavItem}; diff --git a/src/template_engine.rs b/src/template_engine.rs new file mode 100644 index 0000000..8301f5c --- /dev/null +++ b/src/template_engine.rs @@ -0,0 +1,199 @@ +//! Tera-based template engine for runtime HTML generation. + +use std::path::Path; + +use serde::Serialize; +use tera::{Context, Tera}; + +use crate::config::SiteConfig; +use crate::content::{Content, NavItem}; +use crate::error::{Error, Result}; + +/// Runtime template engine wrapping Tera. +pub struct TemplateEngine { + tera: Tera, +} + +impl TemplateEngine { + /// Load templates from a directory (glob pattern: `templates/**/*`). + pub fn new(template_dir: &Path) -> Result { + let pattern = template_dir.join("**/*").display().to_string(); + let tera = Tera::new(&pattern).map_err(|e| Error::TemplateLoad { + message: e.to_string(), + })?; + Ok(Self { tera }) + } + + /// Render a template by name with the given context. + pub fn render(&self, template_name: &str, context: &Context) -> Result { + self.tera + .render(template_name, context) + .map_err(|e| Error::TemplateRender { + template: template_name.to_string(), + message: e.to_string(), + }) + } + + /// Render a standalone page (about, collab, etc.). + pub fn render_page( + &self, + content: &Content, + html_body: &str, + page_path: &str, + config: &SiteConfig, + nav: &[NavItem], + ) -> Result { + let mut ctx = self.base_context(page_path, config, nav); + ctx.insert("page", &FrontmatterContext::from(&content.frontmatter)); + ctx.insert("content", html_body); + self.render("page.html", &ctx) + } + + /// Render a content item (blog post, project, etc.). + pub fn render_content( + &self, + content: &Content, + html_body: &str, + page_path: &str, + config: &SiteConfig, + nav: &[NavItem], + ) -> Result { + let template = content + .frontmatter + .template + .as_deref() + .unwrap_or("content/default.html"); + let mut ctx = self.base_context(page_path, config, nav); + ctx.insert("page", &FrontmatterContext::from(&content.frontmatter)); + ctx.insert("content", html_body); + self.render(template, &ctx) + } + + /// Render a section index page (blog index, projects index). + pub fn render_section( + &self, + section: &Content, + items: &[ContentContext], + page_path: &str, + config: &SiteConfig, + nav: &[NavItem], + ) -> Result { + let section_type = section + .frontmatter + .section_type + .as_deref() + .unwrap_or("default"); + let template = format!("section/{}.html", section_type); + + let mut ctx = self.base_context(page_path, config, nav); + ctx.insert("section", &FrontmatterContext::from(§ion.frontmatter)); + ctx.insert("items", items); + self.render(&template, &ctx) + } + + /// Build base context with common variables. + fn base_context(&self, page_path: &str, config: &SiteConfig, nav: &[NavItem]) -> Context { + let mut ctx = Context::new(); + ctx.insert("config", &ConfigContext::from(config)); + ctx.insert("nav", nav); + ctx.insert("page_path", page_path); + ctx.insert("prefix", &relative_prefix(page_path)); + ctx + } +} + +/// Compute relative path prefix based on page depth. +fn relative_prefix(page_path: &str) -> String { + let depth = page_path.matches('/').count().saturating_sub(1); + if depth == 0 { + ".".to_string() + } else { + (0..depth).map(|_| "..").collect::>().join("/") + } +} + +// ============================================================================ +// Context structs for Tera serialization +// ============================================================================ + +/// Site config context for templates. +#[derive(Serialize)] +pub struct ConfigContext { + pub title: String, + pub author: String, + pub base_url: String, +} + +impl From<&SiteConfig> for ConfigContext { + fn from(config: &SiteConfig) -> Self { + Self { + title: config.title.clone(), + author: config.author.clone(), + base_url: config.base_url.clone(), + } + } +} + +/// Frontmatter context for templates. +#[derive(Serialize)] +pub struct FrontmatterContext { + pub title: String, + pub description: Option, + pub date: Option, + pub tags: Vec, + pub weight: Option, + pub link_to: Option, +} + +impl From<&crate::content::Frontmatter> for FrontmatterContext { + fn from(fm: &crate::content::Frontmatter) -> Self { + Self { + title: fm.title.clone(), + description: fm.description.clone(), + date: fm.date.clone(), + tags: fm.tags.clone(), + weight: fm.weight, + link_to: fm.link_to.clone(), + } + } +} + +/// Content item context for section listings. +#[derive(Serialize)] +pub struct ContentContext { + pub frontmatter: FrontmatterContext, + pub body: String, + pub slug: String, + pub path: String, +} + +impl ContentContext { + pub fn from_content(content: &Content, content_dir: &Path) -> Self { + Self { + frontmatter: FrontmatterContext::from(&content.frontmatter), + body: content.body.clone(), + slug: content.slug.clone(), + path: format!("/{}", content.output_path(content_dir).display()), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_relative_prefix_root() { + assert_eq!(relative_prefix("/index.html"), "."); + } + + #[test] + fn test_relative_prefix_depth_1() { + assert_eq!(relative_prefix("/blog/index.html"), ".."); + } + + #[test] + fn test_relative_prefix_depth_2() { + assert_eq!(relative_prefix("/blog/posts/foo.html"), "../.."); + } +} diff --git a/templates/base.html b/templates/base.html new file mode 100644 index 0000000..490c521 --- /dev/null +++ b/templates/base.html @@ -0,0 +1,25 @@ + + + + + + {{ title }} | {{ config.title }} + + + + + + +
+ {% block content %}{% endblock content %} +
+
+

© {{ config.author }}

+
+ + diff --git a/templates/content/default.html b/templates/content/default.html new file mode 100644 index 0000000..47c66bd --- /dev/null +++ b/templates/content/default.html @@ -0,0 +1,13 @@ +{% extends "base.html" %} + +{% block content %} +
+

{{ page.title }}

+ {% if page.description %} +

{{ page.description }}

+ {% endif %} +
+ {{ content | safe }} +
+
+{% endblock content %} \ No newline at end of file diff --git a/templates/content/post.html b/templates/content/post.html new file mode 100644 index 0000000..3a462f9 --- /dev/null +++ b/templates/content/post.html @@ -0,0 +1,25 @@ +{% extends "base.html" %} + +{% block content %} +
+
+

{{ page.title }}

+ {% if page.date %} + + {% endif %} + {% if page.description %} +

{{ page.description }}

+ {% endif %} + {% if page.tags %} +
    + {% for tag in page.tags %} +
  • {{ tag }}
  • + {% endfor %} +
+ {% endif %} +
+
+ {{ content | safe }} +
+
+{% endblock content %} \ No newline at end of file diff --git a/templates/homepage.html b/templates/homepage.html new file mode 100644 index 0000000..27094e4 --- /dev/null +++ b/templates/homepage.html @@ -0,0 +1,13 @@ +{% extends "base.html" %} + +{% block content %} +
+

{{ page.title }}

+ {% if page.description %} +

{{ page.description }}

+ {% endif %} +
+
+ {{ content | safe }} +
+{% endblock content %} \ No newline at end of file diff --git a/templates/page.html b/templates/page.html new file mode 100644 index 0000000..2094088 --- /dev/null +++ b/templates/page.html @@ -0,0 +1,10 @@ +{% extends "base.html" %} + +{% block content %} +
+

{{ page.title }}

+
+ {{ content | safe }} +
+
+{% endblock content %} \ No newline at end of file diff --git a/templates/section/blog.html b/templates/section/blog.html new file mode 100644 index 0000000..66413bd --- /dev/null +++ b/templates/section/blog.html @@ -0,0 +1,20 @@ +{% extends "base.html" %} + +{% block content %} +

{{ section.title }}

+ +{% endblock content %} \ No newline at end of file diff --git a/templates/section/default.html b/templates/section/default.html new file mode 100644 index 0000000..c1d4449 --- /dev/null +++ b/templates/section/default.html @@ -0,0 +1,15 @@ +{% extends "base.html" %} + +{% block content %} +

{{ section.title }}

+
    + {% for item in items %} +
  • + {{ item.frontmatter.title }} + {% if item.frontmatter.description %} +

    {{ item.frontmatter.description }}

    + {% endif %} +
  • + {% endfor %} +
+{% endblock content %} \ No newline at end of file diff --git a/templates/section/projects.html b/templates/section/projects.html new file mode 100644 index 0000000..eec9477 --- /dev/null +++ b/templates/section/projects.html @@ -0,0 +1,24 @@ +{% extends "base.html" %} + +{% block content %} +

{{ section.title }}

+ +{% endblock content %} \ No newline at end of file