From c203cd8f93bfcfdd60d9a991b1595e2ba8064b8d Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 5 Nov 2025 04:24:24 +0000 Subject: [PATCH 1/3] Add YAML frontmatter extraction from markdown files This adds support for extracting metadata from YAML frontmatter in markdown files, eliminating the need to manually maintain data.json files. Changes: - Added serde, serde_yaml, and toml dependencies for parsing - Created new frontmatter.rs handler to extract YAML frontmatter from .md files - Modified templates.rs to support both .data.toml (new) and .data.json (legacy) - TOML file lists which markdown files to include and in what order - Frontmatter fields become template variables (${title}, ${date}, etc.) - Auto-generates --entry-path, --result-path, and link fields - Only 'title' field is required in frontmatter - Updated README with comprehensive documentation for the new feature File structure example: src/data/Posts/Posts.data.toml # Lists files and order src/data/Posts/my-post.md # Contains YAML frontmatter + content The system falls back to .data.json if .data.toml doesn't exist, maintaining backwards compatibility. --- Cargo.lock | 74 ++++++++++++++++ Cargo.toml | 3 + README.md | 46 +++++++++- src/handlers/frontmatter.rs | 166 ++++++++++++++++++++++++++++++++++++ src/handlers/templates.rs | 72 +++++++++++----- src/main.rs | 1 + 6 files changed, 341 insertions(+), 21 deletions(-) create mode 100644 src/handlers/frontmatter.rs diff --git a/Cargo.lock b/Cargo.lock index e84ebd2..879ec49 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2088,6 +2088,28 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_spanned" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_yaml" +version = "0.9.34+deprecated" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" +dependencies = [ + "indexmap", + "itoa", + "ryu", + "serde", + "unsafe-libyaml", +] + [[package]] name = "sha1" version = "0.10.6" @@ -2148,8 +2170,11 @@ dependencies = [ "num_cpus", "once_cell", "rouille", + "serde", "serde_json", + "serde_yaml", "simple-websockets", + "toml", ] [[package]] @@ -2429,6 +2454,40 @@ dependencies = [ "tungstenite", ] +[[package]] +name = "toml" +version = "0.8.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + +[[package]] +name = "toml_datetime" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.22.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "583c44c02ad26b0c3f3066fe629275e50627026c51ac2e595cca4c230ce1ce1d" +dependencies = [ + "indexmap", + "serde", + "serde_spanned", + "toml_datetime", + "winnow", +] + [[package]] name = "tungstenite" version = "0.19.0" @@ -2511,6 +2570,12 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e" +[[package]] +name = "unsafe-libyaml" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" + [[package]] name = "url" version = "2.5.2" @@ -2779,6 +2844,15 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +[[package]] +name = "winnow" +version = "0.6.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e90edd2ac1aa278a5c4599b1d89cf03074b610800f866d4026dc199d7929a28" +dependencies = [ + "memchr", +] + [[package]] name = "wyz" version = "0.5.1" diff --git a/Cargo.toml b/Cargo.toml index 42698f0..3fbc53c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,8 +19,11 @@ notify = "6.1.1" num_cpus = "1.16.0" once_cell = "1.20.2" rouille = "3.6.2" +serde = { version = "1.0", features = ["derive"] } serde_json = "1.0.120" +serde_yaml = "0.9" simple-websockets = "0.1.6" +toml = "0.8" [[bin]] name = "simple" diff --git a/README.md b/README.md index 7e2842a..e249459 100644 --- a/README.md +++ b/README.md @@ -193,7 +193,51 @@ Will render out to: You can also use the template syntax to render entries from files. -Take this `src/data/Posts.data.json`: +#### Using Frontmatter (Recommended) + +Instead of manually maintaining a JSON file, you can extract metadata from YAML frontmatter in your markdown files: + +**File structure:** +``` +src/data/Posts/ +├── my-first-post.md +├── another-post.md +└── Posts.data.toml +``` + +**`src/data/Posts.data.toml`** - Specifies which files to include and their order: +```toml +files = [ + "my-first-post.md", + "another-post.md" +] +``` + +**`src/data/Posts/my-first-post.md`** - Markdown file with YAML frontmatter: +```markdown +--- +title: My First Post +description: This is my first blog post +date: Jan 1 2025 +--- + +# Content here + +Your markdown content... +``` + +The frontmatter will automatically generate the data needed for templating. The following fields are auto-generated: +- `--entry-path`: Set to the markdown file path +- `--result-path`: Set to `content/{filename}.html` +- `link`: Set to `./content/{filename}.html` + +All other frontmatter fields (like `title`, `description`, `date`) are available as template variables. + +**Required fields:** Only `title` is required in the frontmatter. + +#### Using JSON (Legacy) + +Alternatively, you can use the older JSON format. Take this `src/data/Posts.data.json`: ```json [ diff --git a/src/handlers/frontmatter.rs b/src/handlers/frontmatter.rs new file mode 100644 index 0000000..e732ce7 --- /dev/null +++ b/src/handlers/frontmatter.rs @@ -0,0 +1,166 @@ +use crate::error::{ErrorType, MapProcErr, ProcessError, WithItem}; +use serde::{Deserialize, Serialize}; +use serde_json::Value; +use std::collections::HashMap; +use std::fs; +use std::path::PathBuf; + +#[derive(Debug, Deserialize, Serialize)] +pub struct FileList { + pub files: Vec, +} + +/// Extract YAML frontmatter from markdown content +/// Returns (frontmatter_map, remaining_content) +pub fn extract_frontmatter( + content: &str, +) -> Result<(HashMap, String), ProcessError> { + let content = content.trim_start(); + + if !content.starts_with("---") { + return Err(ProcessError { + error_type: ErrorType::Syntax, + item: WithItem::Data, + path: PathBuf::new(), + message: Some("Frontmatter must start with '---'".to_string()), + }); + } + + let after_first_delimiter = &content[3..]; + + if let Some(end_pos) = after_first_delimiter.find("\n---") { + let frontmatter_str = &after_first_delimiter[..end_pos]; + let remaining = &after_first_delimiter[end_pos + 4..].trim_start(); + + // Parse YAML frontmatter + let yaml_value: serde_yaml::Value = serde_yaml::from_str(frontmatter_str) + .map_err(|e| ProcessError { + error_type: ErrorType::Syntax, + item: WithItem::Data, + path: PathBuf::new(), + message: Some(format!("Failed to parse YAML frontmatter: {}", e)), + })?; + + // Convert YAML to HashMap + let mut map = HashMap::new(); + if let serde_yaml::Value::Mapping(mapping) = yaml_value { + for (key, value) in mapping { + if let serde_yaml::Value::String(k) = &key { + let v = match &value { + serde_yaml::Value::String(s) => s.clone(), + serde_yaml::Value::Number(n) => n.to_string(), + serde_yaml::Value::Bool(b) => b.to_string(), + _ => String::new(), + }; + map.insert(k.clone(), v); + } + } + } + + // Validate that title exists + if !map.contains_key("title") { + return Err(ProcessError { + error_type: ErrorType::Syntax, + item: WithItem::Data, + path: PathBuf::new(), + message: Some("Frontmatter must contain a 'title' field".to_string()), + }); + } + + Ok((map, remaining.to_string())) + } else { + Err(ProcessError { + error_type: ErrorType::Syntax, + item: WithItem::Data, + path: PathBuf::new(), + message: Some("Frontmatter must end with '---'".to_string()), + }) + } +} + +/// Load data from markdown files with frontmatter based on a TOML file list +/// Returns a JSON array value compatible with the existing template system +pub fn load_frontmatter_data( + src: &PathBuf, + name: &str, +) -> Result<(Value, Vec), Vec> { + let mut errors = Vec::new(); + + let toml_path = src + .join("data") + .join(name.replace(":", "/")) + .with_extension("data.toml"); + + // Read the TOML file + let toml_content = fs::read_to_string(&toml_path) + .map_proc_err( + WithItem::Data, + ErrorType::Io, + &toml_path, + Some("Failed to read data.toml file".to_string()), + ) + .map_err(|e| vec![e])?; + + // Parse the TOML file + let file_list: FileList = toml::from_str(&toml_content).map_err(|e| { + vec![ProcessError { + error_type: ErrorType::Syntax, + item: WithItem::Data, + path: toml_path.clone(), + message: Some(format!("Failed to parse TOML: {}", e)), + }] + })?; + + let data_dir = src.join("data").join(name.replace(":", "/")); + let mut items = Vec::new(); + + for file in &file_list.files { + let md_path = data_dir.join(file); + + let content = match fs::read_to_string(&md_path) { + Ok(c) => c, + Err(e) => { + errors.push(ProcessError { + error_type: ErrorType::Io, + item: WithItem::Data, + path: md_path.clone(), + message: Some(format!("Failed to read markdown file: {}", e)), + }); + continue; + } + }; + + let (mut frontmatter, _) = match extract_frontmatter(&content) { + Ok((fm, remaining)) => (fm, remaining), + Err(mut e) => { + e.path = md_path.clone(); + errors.push(e); + continue; + } + }; + + // Generate entry-path and result-path from filename + let file_stem = md_path + .file_stem() + .and_then(|s| s.to_str()) + .unwrap_or("unknown"); + + let relative_entry_path = format!("{}/{}", name.replace(":", "/"), file); + let result_path = format!("content/{}.html", file_stem); + + // Add the special fields + frontmatter.insert("--entry-path".to_string(), relative_entry_path); + frontmatter.insert("--result-path".to_string(), result_path.clone()); + frontmatter.insert("link".to_string(), format!("./{}", result_path)); + + // Convert HashMap to serde_json::Value + let obj: serde_json::Map = frontmatter + .into_iter() + .map(|(k, v)| (k, Value::String(v))) + .collect(); + + items.push(Value::Object(obj)); + } + + Ok((Value::Array(items), errors)) +} diff --git a/src/handlers/templates.rs b/src/handlers/templates.rs index 32c9125..5ede490 100644 --- a/src/handlers/templates.rs +++ b/src/handlers/templates.rs @@ -1,5 +1,6 @@ use crate::error::{ErrorType, MapProcErr, ProcessError, WithItem}; use crate::handlers::entries::process_entry; +use crate::handlers::frontmatter::load_frontmatter_data; use crate::handlers::pages::page; use crate::utils::kv_replace; use crate::utils::ProcessResult; @@ -47,37 +48,68 @@ pub fn get_template(src: &PathBuf, name: &str, mut hist: HashSet) -> Pr .inspect_err(|e| errors.push((*e).clone())) .unwrap_or_else(|_| String::new()); - let data = fs::read_to_string(&data_path) - .map_proc_err( - WithItem::Data, - ErrorType::Io, - &data_path, - Some("Failed to read data file".to_string()), - ) - .inspect_err(|e| errors.push((*e).clone())) - .unwrap_or_else(|_| String::new()); - - if template.is_empty() || data.is_empty() { + if template.is_empty() { return ProcessResult { output: String::new(), errors, }; } - let v: Value = match serde_json::from_str(&data) { - Ok(value) => value, - Err(e) => { - errors.push(ProcessError { - error_type: ErrorType::Syntax, - item: WithItem::Data, - path: data_path, - message: Some(format!("JSON decode error: {}", e)), - }); + // Try to load from .toml + frontmatter first, fall back to .json + let toml_path = src + .join("data") + .join(name.replace(":", "/")) + .with_extension("data.toml"); + + let v: Value = if toml_path.exists() { + // Use frontmatter-based loading + match load_frontmatter_data(src, name) { + Ok((value, fm_errors)) => { + errors.extend(fm_errors); + value + } + Err(fm_errors) => { + errors.extend(fm_errors); + return ProcessResult { + output: String::new(), + errors, + }; + } + } + } else { + // Fall back to JSON loading + let data = fs::read_to_string(&data_path) + .map_proc_err( + WithItem::Data, + ErrorType::Io, + &data_path, + Some("Failed to read data file".to_string()), + ) + .inspect_err(|e| errors.push((*e).clone())) + .unwrap_or_else(|_| String::new()); + + if data.is_empty() { return ProcessResult { output: String::new(), errors, }; } + + match serde_json::from_str(&data) { + Ok(value) => value, + Err(e) => { + errors.push(ProcessError { + error_type: ErrorType::Syntax, + item: WithItem::Data, + path: data_path, + message: Some(format!("JSON decode error: {}", e)), + }); + return ProcessResult { + output: String::new(), + errors, + }; + } + } }; let items = match v.as_array() { diff --git a/src/main.rs b/src/main.rs index 3bc08b0..66b62ad 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,7 @@ mod handlers { pub mod components; pub mod entries; + pub mod frontmatter; pub mod katex_assets; pub mod markdown; pub mod pages; From 864b5c84ed0314855d9f75bf50afcfa5548f780d Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 5 Nov 2025 04:52:29 +0000 Subject: [PATCH 2/3] Strip frontmatter from markdown before rendering and add working example This commit fixes the issue where YAML frontmatter was being rendered as part of the markdown content, and adds a complete working example demonstrating the frontmatter feature. Changes: - Modified entries.rs to strip frontmatter before wrapping content in tags - Falls back to original content if frontmatter extraction fails (for files without frontmatter) - Added comprehensive example directory with: - 3 sample blog posts with different frontmatter fields - TOML configuration file for file ordering - Template and frame files for rendering - Index page demonstrating the template system - Complete README with usage instructions The example demonstrates: - YAML frontmatter extraction - TOML-based file ordering - Auto-generated paths and links - Template variable substitution - Proper markdown rendering without frontmatter - Backwards compatibility Tested and verified: all files build correctly and frontmatter fields are properly substituted in both list view and individual post pages. --- example/README.md | 105 ++++++++++++++++++++ example/src/data/Posts.data.toml | 8 ++ example/src/data/Posts/advanced-features.md | 89 +++++++++++++++++ example/src/data/Posts/getting-started.md | 71 +++++++++++++ example/src/data/Posts/my-first-post.md | 35 +++++++ example/src/pages/index.html | 80 +++++++++++++++ example/src/templates/Posts.frame.html | 81 +++++++++++++++ example/src/templates/Posts.template.html | 13 +++ src/handlers/entries.rs | 12 ++- 9 files changed, 493 insertions(+), 1 deletion(-) create mode 100644 example/README.md create mode 100644 example/src/data/Posts.data.toml create mode 100644 example/src/data/Posts/advanced-features.md create mode 100644 example/src/data/Posts/getting-started.md create mode 100644 example/src/data/Posts/my-first-post.md create mode 100644 example/src/pages/index.html create mode 100644 example/src/templates/Posts.frame.html create mode 100644 example/src/templates/Posts.template.html diff --git a/example/README.md b/example/README.md new file mode 100644 index 0000000..1c279f1 --- /dev/null +++ b/example/README.md @@ -0,0 +1,105 @@ +# Simple Frontmatter Example + +This example demonstrates how to use YAML frontmatter in markdown files with Simple. + +## Structure + +``` +example/ +├── src/ +│ ├── data/ +│ │ ├── Posts.data.toml # Configuration listing files +│ │ └── Posts/ +│ │ ├── my-first-post.md # Blog post with frontmatter +│ │ ├── getting-started.md # Another blog post +│ │ └── advanced-features.md # Yet another post +│ ├── templates/ +│ │ ├── Posts.template.html # Template for post cards +│ │ └── Posts.frame.html # Frame for individual post pages +│ ├── pages/ +│ │ └── index.html # Main page that lists all posts +│ └── public/ +│ └── (static assets go here) +``` + +**Important**: The TOML configuration file (`Posts.data.toml`) goes in `src/data/`, while the markdown files go in `src/data/Posts/`. + +## How It Works + +1. **Markdown files with frontmatter**: Each `.md` file in `data/Posts/` contains YAML frontmatter with metadata: + ```markdown + --- + title: My Post + description: A great post + date: Jan 15 2025 + author: John Doe + --- + + # Content here... + ``` + +2. **TOML configuration**: `Posts.data.toml` specifies which files to include and in what order: + ```toml + files = [ + "my-first-post.md", + "getting-started.md", + "advanced-features.md" + ] + ``` + +3. **Template file**: `Posts.template.html` defines how each post appears in the list on the index page + +4. **Frame file**: `Posts.frame.html` defines the layout for individual post pages + +5. **Index page**: `index.html` uses `<-Template{Posts} />` to render all posts + +## Building + +From the repository root: + +```bash +# Build the example +./target/release/simple build example + +# Or run in development mode +./target/release/simple dev example +``` + +The built site will be in `example/dist/`. + +## What Gets Generated + +- `dist/index.html` - Main page listing all posts +- `dist/content/my-first-post.html` - Individual post page +- `dist/content/getting-started.html` - Individual post page +- `dist/content/advanced-features.html` - Individual post page + +## Key Features Demonstrated + +✅ YAML frontmatter extraction +✅ TOML-based file ordering +✅ Auto-generated paths and links +✅ Template variable substitution +✅ Markdown rendering in frames +✅ Backwards compatibility (can still use `.data.json`) + +## Customization + +You can add any fields you want to the frontmatter: + +```yaml +--- +title: Required field +description: Optional +author: Optional +date: Optional +tags: Optional +readtime: Optional +custom_field: Optional +anything_you_want: Optional +--- +``` + +All fields become available as template variables: `${title}`, `${author}`, `${custom_field}`, etc. + +Only `title` is required! diff --git a/example/src/data/Posts.data.toml b/example/src/data/Posts.data.toml new file mode 100644 index 0000000..5d51322 --- /dev/null +++ b/example/src/data/Posts.data.toml @@ -0,0 +1,8 @@ +# List of markdown files to include as blog posts +# Files are processed in the order listed here + +files = [ + "my-first-post.md", + "getting-started.md", + "advanced-features.md" +] diff --git a/example/src/data/Posts/advanced-features.md b/example/src/data/Posts/advanced-features.md new file mode 100644 index 0000000..ceec591 --- /dev/null +++ b/example/src/data/Posts/advanced-features.md @@ -0,0 +1,89 @@ +--- +title: Advanced Features in Simple +description: Exploring components, slots, and custom templates +date: Jan 25 2025 +author: John Doe +tags: advanced, components, templates +readtime: 8 min +featured: true +--- + +# Advanced Features in Simple + +Now that you're familiar with the basics, let's explore some of Simple's more advanced features. + +## Components with Props + +Components can accept props to make them more flexible: + +```html + +``` + +In your component file: + +```html + +``` + +## Nested Components + +You can organize components in folders: + +```html + + + +``` + +## Template Entries with Frontmatter + +The real power comes from combining templates with frontmatter. Instead of maintaining separate JSON files, you can extract all metadata directly from your markdown files! + +### Before (the old way): + +```json +{ + "title": "My Post", + "description": "...", + "link": "./content/my-post.html", + "--entry-path": "Posts/my-post.md", + "--result-path": "content/my-post.html" +} +``` + +### After (the new way): + +```markdown +--- +title: My Post +description: ... +--- + +Content here... +``` + +Much cleaner! The `--entry-path`, `--result-path`, and `link` fields are automatically generated. + +## Custom Markdown Rendering + +You can use the `` component anywhere: + +```html + + # This is rendered as markdown + + **Bold text** and *italic text* + +``` + +## Tips and Tricks + +1. **Use frame files** for consistent layouts across entries +2. **Keep components small** and focused on one thing +3. **Use frontmatter** for all your metadata needs +4. **Leverage slots** for flexible component content + +Happy building! diff --git a/example/src/data/Posts/getting-started.md b/example/src/data/Posts/getting-started.md new file mode 100644 index 0000000..1bda722 --- /dev/null +++ b/example/src/data/Posts/getting-started.md @@ -0,0 +1,71 @@ +--- +title: Getting Started with Simple +description: A comprehensive guide to setting up your first Simple project +date: Jan 20 2025 +author: John Doe +tags: tutorial, beginner +readtime: 5 min +--- + +# Getting Started with Simple + +In this post, I'll walk you through setting up your first project with Simple. + +## Installation + +First, you'll need to install Simple. The easiest way is to use cargo: + +```bash +cargo install simple-web +``` + +## Project Structure + +A typical Simple project looks like this: + +``` +my-project/ +├── src/ +│ ├── components/ # Reusable UI components +│ ├── data/ # Data files (TOML, JSON, or Markdown) +│ ├── pages/ # Your site pages +│ ├── public/ # Static assets +│ └── templates/ # Templates for dynamic content +``` + +## Creating Your First Page + +Create a file at `src/pages/index.html`: + +```html + + + + My Site + + +

Hello World!

+ <-Template{Posts} /> + + +``` + +## Building Your Site + +Run the build command: + +```bash +simple build my-project +``` + +Your compiled site will be in the `dist/` directory! + +## Development Mode + +For live reloading during development: + +```bash +simple dev my-project +``` + +This starts a local server and watches for changes. diff --git a/example/src/data/Posts/my-first-post.md b/example/src/data/Posts/my-first-post.md new file mode 100644 index 0000000..f3f6d09 --- /dev/null +++ b/example/src/data/Posts/my-first-post.md @@ -0,0 +1,35 @@ +--- +title: My First Blog Post +description: Welcome to my new blog built with Simple! +date: Jan 15 2025 +author: John Doe +tags: introduction, getting-started +--- + +# Welcome to My Blog + +This is my very first blog post using the Simple build tool! I'm excited to share my thoughts and experiences with you. + +## What is Simple? + +Simple is a lightweight static site generator that makes it easy to build fast, modern websites. It uses: + +- Components for reusable UI elements +- Templates for dynamic content +- Markdown for easy content authoring +- YAML frontmatter for metadata + +## Getting Started + +To create your own blog post, simply: + +1. Create a new `.md` file in your `data/Posts/` directory +2. Add YAML frontmatter with your metadata +3. Write your content in Markdown +4. Add the filename to your `Posts.data.toml` file + +That's it! Simple will automatically generate your blog post page. + +## Next Steps + +Check out my other posts to learn more about using Simple and building great websites! diff --git a/example/src/pages/index.html b/example/src/pages/index.html new file mode 100644 index 0000000..9c90c15 --- /dev/null +++ b/example/src/pages/index.html @@ -0,0 +1,80 @@ + + + + + + My Blog - Built with Simple + + + +
+

🚀 My Blog

+

Powered by Simple - A lightweight static site generator

+
+ +
+ <-Template{Posts} /> +
+ +
+

Built with ❤️ using Simple

+
+ + diff --git a/example/src/templates/Posts.frame.html b/example/src/templates/Posts.frame.html new file mode 100644 index 0000000..5e75742 --- /dev/null +++ b/example/src/templates/Posts.frame.html @@ -0,0 +1,81 @@ + + + + + + + ${title} - My Blog + + + + + ← Back to all posts + +
+

${title}

+
+ 📅 ${date} + ✍️ ${author} + ⏱️ ${readtime} +
+
+ +
+ ${--content} +
+ +
+

Tags: ${tags}

+
+ + diff --git a/example/src/templates/Posts.template.html b/example/src/templates/Posts.template.html new file mode 100644 index 0000000..265d6e3 --- /dev/null +++ b/example/src/templates/Posts.template.html @@ -0,0 +1,13 @@ + + diff --git a/src/handlers/entries.rs b/src/handlers/entries.rs index 824247f..4a2c023 100644 --- a/src/handlers/entries.rs +++ b/src/handlers/entries.rs @@ -1,5 +1,6 @@ use crate::dev::{SCRIPT, WS_PORT}; use crate::error::{ErrorType, ProcessError, WithItem}; +use crate::handlers::frontmatter::extract_frontmatter; use crate::handlers::katex_assets; use crate::handlers::pages::page; use crate::utils::kv_replace; @@ -69,9 +70,18 @@ pub fn process_entry( }; let processed_content = if entry_path.extension().and_then(|s| s.to_str()) == Some("md") { + // Strip frontmatter from markdown content before rendering + let content_without_frontmatter = match extract_frontmatter(&content) { + Ok((_, remaining)) => remaining, + Err(_) => { + // If frontmatter extraction fails, use content as-is (might not have frontmatter) + content.clone() + } + }; + frame_content.replace( "${--content}", - &("\n".to_owned() + &content + ""), + &("\n".to_owned() + &content_without_frontmatter + ""), ) } else { frame_content.replace("${--content}", &content) From 3a803a99ec4da63241f0c58710c389ffecb3ecab Mon Sep 17 00:00:00 2001 From: Enoch Date: Tue, 4 Nov 2025 23:59:12 -0500 Subject: [PATCH 3/3] Remove Key Features section from README Removed the 'Key Features Demonstrated' section from README. --- example/README.md | 9 --------- 1 file changed, 9 deletions(-) diff --git a/example/README.md b/example/README.md index 1c279f1..5e3ddff 100644 --- a/example/README.md +++ b/example/README.md @@ -74,15 +74,6 @@ The built site will be in `example/dist/`. - `dist/content/getting-started.html` - Individual post page - `dist/content/advanced-features.html` - Individual post page -## Key Features Demonstrated - -✅ YAML frontmatter extraction -✅ TOML-based file ordering -✅ Auto-generated paths and links -✅ Template variable substitution -✅ Markdown rendering in frames -✅ Backwards compatibility (can still use `.data.json`) - ## Customization You can add any fields you want to the frontmatter: