Spoiler layout & fixes

This commit is contained in:
ef3d0c3e 2024-10-31 11:03:09 +01:00
parent 448739132a
commit fc7ff70090
5 changed files with 169 additions and 25 deletions

View file

@ -60,3 +60,17 @@ If you wish to modify the relative width of the splits: add `style=flex: 0.5` in
####+* Properties ####+* Properties
* ``style`` Added css style to the div (defaults to none) * ``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 `<details>/<summary>` to create the desired layout.
####+* Properties
* ``title`` The spoiler title

View file

@ -351,7 +351,7 @@ impl Rule for CustomStyleRule {
tokens: CustomStyleToken::Toggle(token), tokens: CustomStyleToken::Toggle(token),
name: name.clone(), name: name.clone(),
start: unsafe { std::mem::transmute(on_start.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| { CTX.with_borrow(|ctx| {
@ -414,7 +414,7 @@ impl Rule for CustomStyleRule {
tokens: CustomStyleToken::Pair(token_start, token_end), tokens: CustomStyleToken::Pair(token_start, token_end),
name: name.clone(), name: name.clone(),
start: unsafe { std::mem::transmute(on_start.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| { CTX.with_borrow(|ctx| {
@ -476,8 +476,8 @@ end
function red_style_end() function red_style_end()
nml.raw.push("inline", "</a>") nml.raw.push("inline", "</a>")
end end
nml.custom_style.define_toggled("My Style", "|", "my_style_start()", "my_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()") nml.custom_style.define_toggled("My Style2", "°", red_style_start, red_style_end)
>% >%
pre |styled| post °Hello°. pre |styled| post °Hello°.
"# "#
@ -525,8 +525,8 @@ end
function red_style_end() function red_style_end()
nml.raw.push("inline", "</a>") nml.raw.push("inline", "</a>")
end end
nml.custom_style.define_paired("My Style", "[", "]", "my_style_start()", "my_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()") nml.custom_style.define_paired("My Style2", "(", ")", red_style_start, red_style_end)
>% >%
pre [styled] post (Hello). pre [styled] post (Hello).
"# "#

View file

@ -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<usize> { 1..1 }
fn parse_properties(&self, properties: &str) -> Result<Option<Box<dyn Any>>, 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<String, ()> {
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<Box<dyn Any>>,
compiler: &Compiler,
_document: &dyn Document,
) -> Result<String, String> {
match compiler.target() {
Target::HTML => {
let title = properties
.as_ref()
.unwrap()
.downcast_ref::<String>()
.unwrap();
match token {
LayoutToken::Begin => Ok(format!(
r#"<details class="spoiler"><summary>{}</summary>"#, Compiler::sanitize(compiler.target(), title)
)),
LayoutToken::End => Ok(r#"</details>"#.to_string()),
_ => panic!()
}
}
_ => todo!(""),
}
}
}
} }
#[derive(Debug)] #[derive(Debug)]
@ -254,7 +329,10 @@ impl RuleState for LayoutState {
let mut reports = vec![]; let mut reports = vec![];
let doc_borrow = document.content().borrow(); 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 { for (tokens, layout_type) in &self.stack {
let start = tokens.first().unwrap(); let start = tokens.first().unwrap();
@ -909,6 +987,7 @@ impl RegexRule for LayoutRule {
fn register_layouts(&self, holder: &mut LayoutHolder) { fn register_layouts(&self, holder: &mut LayoutHolder) {
holder.insert(Rc::new(default_layouts::Centered::default())); holder.insert(Rc::new(default_layouts::Centered::default()));
holder.insert(Rc::new(default_layouts::Split::default())); holder.insert(Rc::new(default_layouts::Split::default()));
holder.insert(Rc::new(default_layouts::Spoiler::default()));
} }
} }
@ -942,6 +1021,9 @@ mod tests {
E E
#+LAYOUT_END #+LAYOUT_END
#+LAYOUT_END #+LAYOUT_END
#+LAYOUT_BEGIN[title=F] Spoiler
F
#+LAYOUT_END
"# "#
.to_string(), .to_string(),
None, None,
@ -978,6 +1060,12 @@ mod tests {
}; };
Layout { token == LayoutToken::End, id == 2 }; Layout { token == LayoutToken::End, id == 2 };
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 E
%<nml.layout.push("End", "Split", "")>% %<nml.layout.push("End", "Split", "")>%
%<nml.layout.push("End", "Split", "")>% %<nml.layout.push("End", "Split", "")>%
%<nml.layout.push("Begin", "Spoiler", "title=Test Spoiler")>%
F
%<nml.layout.push("End", "Spoiler", "")>%
"# "#
.to_string(), .to_string(),
None, None,
@ -1035,6 +1127,12 @@ mod tests {
}; };
Layout { token == LayoutToken::End, id == 2 }; Layout { token == LayoutToken::End, id == 2 };
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 };
); );
} }

View file

@ -105,20 +105,28 @@ impl<'b> Parser for LangParser<'b> {
.downcast_rc::<SourceFile>() .downcast_rc::<SourceFile>()
.ok() .ok()
.map(|source| { .map(|source| {
let start = if source.path().starts_with("file:///") { if source.path().is_empty() // Test mode
7 {
} else { None
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(); else
path {
}); let start = if source.path().starts_with("file:///") {
if let Some(path) = path { 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) { if let Err(err) = std::env::set_current_dir(&path) {
eprintln!( eprintln!(
"Failed to set working directory to `{}`: {err}", "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(&current_dir) { if path.is_some()
println!( {
"Failed to set working directory to `{}`: {err} {source:#?}", if let Err(err) = std::env::set_current_dir(&current_dir) {
current_dir.to_str().unwrap_or("") println!(
); "Failed to set working directory to `{}`: {err} {source:#?}",
current_dir.to_str().unwrap_or("")
);
}
} }
(Box::new(doc), state) (Box::new(doc), state)

View file

@ -35,6 +35,27 @@ div.split-container > div.split {
margin: 0.5em; 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 */ /* Styles */
em { em {
padding-left: .1em; padding-left: .1em;