From fc7ff7009078a07501b866807825fffe01f584f6 Mon Sep 17 00:00:00 2001 From: ef3d0c3e Date: Thu, 31 Oct 2024 11:03:09 +0100 Subject: [PATCH] Spoiler layout & fixes --- docs/styles/layout.nml | 14 +++++ src/elements/customstyle.rs | 12 ++--- src/elements/layout.rs | 100 +++++++++++++++++++++++++++++++++++- src/parser/langparser.rs | 47 ++++++++++------- style.css | 21 ++++++++ 5 files changed, 169 insertions(+), 25 deletions(-) diff --git a/docs/styles/layout.nml b/docs/styles/layout.nml index 41eac58..4c90660 100644 --- a/docs/styles/layout.nml +++ b/docs/styles/layout.nml @@ -60,3 +60,17 @@ If you wish to modify the relative width of the splits: add `style=flex: 0.5` in ####+* Properties * ``style`` Added css style to the div (defaults to none) + +## Spoiler + +The spoiler layout creates a collapsed element which can be opened. + +#+LAYOUT_BEGIN[title=Spoiler demo] Spoiler +This content is *hidden*. +#+LAYOUT_END + +####+* Style +The ``Spoiler`` layout uses the `.spoiler` class, combined with `
/` to create the desired layout. + +####+* Properties + * ``title`` The spoiler title diff --git a/src/elements/customstyle.rs b/src/elements/customstyle.rs index f2a8286..ff0112f 100644 --- a/src/elements/customstyle.rs +++ b/src/elements/customstyle.rs @@ -351,7 +351,7 @@ impl Rule for CustomStyleRule { tokens: CustomStyleToken::Toggle(token), name: name.clone(), start: unsafe { std::mem::transmute(on_start.clone()) }, - end: unsafe { std::mem::transmute(on_start.clone()) }, + end: unsafe { std::mem::transmute(on_end.clone()) }, }; CTX.with_borrow(|ctx| { @@ -414,7 +414,7 @@ impl Rule for CustomStyleRule { tokens: CustomStyleToken::Pair(token_start, token_end), name: name.clone(), start: unsafe { std::mem::transmute(on_start.clone()) }, - end: unsafe { std::mem::transmute(on_start.clone()) }, + end: unsafe { std::mem::transmute(on_end.clone()) }, }; CTX.with_borrow(|ctx| { @@ -476,8 +476,8 @@ end function red_style_end() nml.raw.push("inline", "") end -nml.custom_style.define_toggled("My Style", "|", "my_style_start()", "my_style_end()") -nml.custom_style.define_toggled("My Style2", "°", "red_style_start()", "red_style_end()") +nml.custom_style.define_toggled("My Style", "|", my_style_start, my_style_end) +nml.custom_style.define_toggled("My Style2", "°", red_style_start, red_style_end) >% pre |styled| post °Hello°. "# @@ -525,8 +525,8 @@ end function red_style_end() nml.raw.push("inline", "") end -nml.custom_style.define_paired("My Style", "[", "]", "my_style_start()", "my_style_end()") -nml.custom_style.define_paired("My Style2", "(", ")", "red_style_start()", "red_style_end()") +nml.custom_style.define_paired("My Style", "[", "]", my_style_start, my_style_end) +nml.custom_style.define_paired("My Style2", "(", ")", red_style_start, red_style_end) >% pre [styled] post (Hello). "# diff --git a/src/elements/layout.rs b/src/elements/layout.rs index 1b86a08..fb1b95b 100644 --- a/src/elements/layout.rs +++ b/src/elements/layout.rs @@ -216,6 +216,81 @@ mod default_layouts { } } } + + #[derive(Debug)] + pub struct Spoiler(PropertyParser); + + impl Default for Spoiler { + fn default() -> Self { + let mut properties = HashMap::new(); + properties.insert( + "title".to_string(), + Property::new( + true, + "Spoiler title".to_string(), + Some("".to_string()) + ), + ); + + Self(PropertyParser { properties }) + } + } + + impl LayoutType for Spoiler { + fn name(&self) -> &'static str { "Spoiler" } + + fn expects(&self) -> Range { 1..1 } + + fn parse_properties(&self, properties: &str) -> Result>, String> { + let props = if properties.is_empty() { + self.0.default() + } else { + self.0.parse(properties) + } + .map_err(|err| { + format!( + "Failed to parse properties for layout {}: {err}", + self.name() + ) + })?; + + let title = props + .get("title", |_, value| -> Result { + Ok(value.clone()) + }) + .map_err(|err| format!("Failed to parse style: {err:#?}")) + .map(|(_, value)| value)?; + + Ok(Some(Box::new(title))) + } + + fn compile( + &self, + token: LayoutToken, + _id: usize, + properties: &Option>, + compiler: &Compiler, + _document: &dyn Document, + ) -> Result { + match compiler.target() { + Target::HTML => { + let title = properties + .as_ref() + .unwrap() + .downcast_ref::() + .unwrap(); + match token { + LayoutToken::Begin => Ok(format!( + r#"
{}"#, Compiler::sanitize(compiler.target(), title) + )), + LayoutToken::End => Ok(r#"
"#.to_string()), + _ => panic!() + } + } + _ => todo!(""), + } + } + } } #[derive(Debug)] @@ -254,7 +329,10 @@ impl RuleState for LayoutState { let mut reports = vec![]; let doc_borrow = document.content().borrow(); - let at = doc_borrow.last().unwrap().location(); + let at = doc_borrow.last().map_or( + Token::new(document.source().content().len()..document.source().content().len(), document.source()), + |last| last.location().to_owned() + ); for (tokens, layout_type) in &self.stack { let start = tokens.first().unwrap(); @@ -909,6 +987,7 @@ impl RegexRule for LayoutRule { fn register_layouts(&self, holder: &mut LayoutHolder) { holder.insert(Rc::new(default_layouts::Centered::default())); holder.insert(Rc::new(default_layouts::Split::default())); + holder.insert(Rc::new(default_layouts::Spoiler::default())); } } @@ -942,6 +1021,9 @@ mod tests { E #+LAYOUT_END #+LAYOUT_END +#+LAYOUT_BEGIN[title=F] Spoiler + F +#+LAYOUT_END "# .to_string(), None, @@ -978,6 +1060,12 @@ mod tests { }; Layout { token == LayoutToken::End, id == 2 }; Layout { token == LayoutToken::End, id == 2 }; + + Layout { token == LayoutToken::Begin, id == 0 }; + Paragraph { + Text { content == "F" }; + }; + Layout { token == LayoutToken::End, id == 1 }; ); } @@ -999,6 +1087,10 @@ mod tests { E %% %% + +%% + F +%% "# .to_string(), None, @@ -1035,6 +1127,12 @@ mod tests { }; Layout { token == LayoutToken::End, id == 2 }; Layout { token == LayoutToken::End, id == 2 }; + Paragraph; + Layout { token == LayoutToken::Begin, id == 0 }; + Paragraph { + Text { content == "F" }; + }; + Layout { token == LayoutToken::End, id == 1 }; ); } diff --git a/src/parser/langparser.rs b/src/parser/langparser.rs index cd89a10..6d7792f 100644 --- a/src/parser/langparser.rs +++ b/src/parser/langparser.rs @@ -105,20 +105,28 @@ impl<'b> Parser for LangParser<'b> { .downcast_rc::() .ok() .map(|source| { - let start = if source.path().starts_with("file:///") { - 7 - } else { - 0 - }; - let mut path = PathBuf::from(&source.path()[start..]); - match path.canonicalize() { - Ok(cano) => path = cano, - Err(err) => eprintln!("Failed to canonicalize path `{}`: {err}", source.path()), + if source.path().is_empty() // Test mode + { + None } - path.pop(); - path - }); - if let Some(path) = path { + else + { + let start = if source.path().starts_with("file:///") { + 7 + } else { + 0 + }; + let mut path = PathBuf::from(&source.path()[start..]); + match path.canonicalize() { + Ok(cano) => path = cano, + Err(err) => eprintln!("Failed to canonicalize path `{}`: {err}", source.path()), + } + path.pop(); + Some(path) + } + }) + .unwrap(); + if let Some(path) = &path { if let Err(err) = std::env::set_current_dir(&path) { eprintln!( "Failed to set working directory to `{}`: {err}", @@ -209,11 +217,14 @@ impl<'b> Parser for LangParser<'b> { ); } - if let Err(err) = std::env::set_current_dir(¤t_dir) { - println!( - "Failed to set working directory to `{}`: {err} {source:#?}", - current_dir.to_str().unwrap_or("") - ); + if path.is_some() + { + if let Err(err) = std::env::set_current_dir(¤t_dir) { + println!( + "Failed to set working directory to `{}`: {err} {source:#?}", + current_dir.to_str().unwrap_or("") + ); + } } (Box::new(doc), state) diff --git a/style.css b/style.css index f18933e..8d5933c 100644 --- a/style.css +++ b/style.css @@ -35,6 +35,27 @@ div.split-container > div.split { margin: 0.5em; } +details.spoiler { + border: 1px solid #235; + border-radius: 4px; + padding: 0.5em 0.5em 0; +} + +details.spoiler summary { + margin: -0.5em -0.5em 0; + padding: 0.5em; +} + +details[open].spoiler { + border: 1px solid #235; + border-radius: 4px; +} + +details[open].spoiler summary { + border-bottom: 1px solid #235; + padding: .5em; +} + /* Styles */ em { padding-left: .1em;