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
|
####+* 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
|
||||||
|
|
|
@ -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).
|
||||||
"#
|
"#
|
||||||
|
|
|
@ -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 };
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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(¤t_dir) {
|
if path.is_some()
|
||||||
println!(
|
{
|
||||||
"Failed to set working directory to `{}`: {err} {source:#?}",
|
if let Err(err) = std::env::set_current_dir(¤t_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)
|
||||||
|
|
21
style.css
21
style.css
|
@ -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;
|
||||||
|
|
Loading…
Reference in a new issue