From e8c828db53dfba5acff61d1539c864d0039adec2 Mon Sep 17 00:00:00 2001 From: Lachlan Kermode Date: Tue, 21 Jul 2026 14:20:40 +0200 Subject: [PATCH] Ships the default HTML stylesheet as a linked asset, not inline The built-in default stylesheet was inlined as a "); - } - - if let Some(pos) = html.find("") { - let mut result = String::with_capacity(html.len() + styles.len()); - result.push_str(&html[..pos]); - result.push_str(&styles); - result.push_str(&html[pos..]); - Ok(result) - } else { - Err(RheoError::HtmlGeneration { - count: 1, - errors: "HTML document does not contain a element".to_string(), - }) - } +/// The `../` prefix that makes a build-root asset ref resolve from a page at the +/// given output path. `index.html` → `""`; `chapters/ch1.html` → `"../"`; +/// `a/b/c.html` → `"../../"`. Assets are written relative to the plugin output +/// root, so a page one directory deep must climb one level to reach them. +pub fn depth_prefix(output_rel_path: &str) -> String { + "../".repeat(output_rel_path.matches('/').count()) } // ─── Tests ──────────────────────────────────────────────────────────────────── @@ -549,70 +496,11 @@ mod tests { assert!(serialized.contains("")); } - // inject_inline_styles tests (free fn — kept for backwards compat) - - #[test] - fn test_inject_inline_styles_basic() { - let html = ""; - let result = inject_inline_styles(html, &["body { color: red; }"]).unwrap(); - assert!(result.contains("")); - } - #[test] - fn test_inject_inline_styles_empty() { - let html = ""; - let result = inject_inline_styles(html, &[]).unwrap(); - assert_eq!(result, html); - } - - #[test] - fn test_inject_inline_styles_no_head() { - let html = ""; - let result = inject_inline_styles(html, &["body {}"]); - assert!(result.is_err()); - } - - // HtmlDom::inject_inline_styles tests - - #[test] - fn test_dom_inject_inline_styles_basic() { - let html = ""; - let mut dom = HtmlDom::parse(html).unwrap(); - dom.inject_inline_styles(&["body { color: red; }"]).unwrap(); - let result = dom.serialize().unwrap(); - assert!(result.contains("")); - } - - #[test] - fn test_dom_inject_inline_styles_raw_chars_unescaped() { - let html = ""; - let mut dom = HtmlDom::parse(html).unwrap(); - dom.inject_inline_styles(&["p > span { content: \"a & b\"; }"]) - .unwrap(); - let result = dom.serialize().unwrap(); - assert!(result.contains("p > span { content: \"a & b\"; }")); - assert!(!result.contains(">")); - assert!(!result.contains("&")); - } - - #[test] - fn test_dom_inject_inline_styles_empty() { - let html = ""; - let mut dom = HtmlDom::parse(html).unwrap(); - dom.inject_inline_styles(&[]).unwrap(); - let result = dom.serialize().unwrap(); - assert!(!result.contains("")); - assert!(result.contains("")); + fn test_depth_prefix() { + assert_eq!(depth_prefix("index.html"), ""); + assert_eq!(depth_prefix("chapters/ch1.html"), "../"); + assert_eq!(depth_prefix("a/b/c.html"), "../../"); } // inject_head_links tests (via HtmlDom) diff --git a/crates/html/src/lib.rs b/crates/html/src/lib.rs index f1fc6ced..05055ce0 100644 --- a/crates/html/src/lib.rs +++ b/crates/html/src/lib.rs @@ -7,9 +7,13 @@ use serde::Deserialize; /// Used when the project doesn't provide its own style.css. pub const DEFAULT_STYLESHEET: &str = include_str!("templates/style.css"); +/// Output filename for the bundled default stylesheet when no user CSS resolves. +/// Distinct from `style.css` so it never clashes with a user's own stylesheet. +pub const DEFAULT_STYLESHEET_NAME: &str = "rheo-default.css"; + use rheo_core::{ - AssetConfig, CastVertebra, FormatInitTemplate, FormatPlugin, OpenHandle, PluginContext, Result, - RheoError, ServerHandle, + AssetConfig, CastVertebra, EmbeddedDefault, FormatInitTemplate, FormatPlugin, OpenHandle, + PluginContext, Result, RheoError, ServerHandle, }; use std::path::Path; use tracing::{debug, info, warn}; @@ -93,11 +97,18 @@ impl FormatPlugin for HtmlPlugin { name: STYLESHEETS, default_path: "style.css", required: false, + // No user/override stylesheet → ship the built-in default as a + // real linked file (rheo-default.css) rather than an inline