Spoiler layout & fixes
This commit is contained in:
parent
448739132a
commit
fc7ff70090
5 changed files with 169 additions and 25 deletions
|
@ -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 `<details>/<summary>` to create the desired layout.
|
||||
|
||||
####+* Properties
|
||||
* ``title`` The spoiler title
|
||||
|
|
|
@ -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", "</a>")
|
||||
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", "</a>")
|
||||
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).
|
||||
"#
|
||||
|
|
|
@ -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)]
|
||||
|
@ -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
|
||||
%<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(),
|
||||
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 };
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -105,20 +105,28 @@ impl<'b> Parser for LangParser<'b> {
|
|||
.downcast_rc::<SourceFile>()
|
||||
.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)
|
||||
|
|
21
style.css
21
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;
|
||||
|
|
Loading…
Reference in a new issue