Hide cw pictures behind a summary/details (#483)

* Hide cw pictures behind a summary/details
* refactor md_to_html a bit and add cw support
* use random id for cw checkbox
This commit is contained in:
fdb-hiroshima
2019-04-06 19:20:33 +02:00
committed by GitHub
parent eabe73ddc0
commit 12c2078c89
11 changed files with 239 additions and 63 deletions
+105 -41
View File
@@ -56,53 +56,117 @@ fn to_inline(tag: Tag) -> Tag {
}
}
fn flatten_text<'a>(state: &mut Option<String>, evt: Event<'a>) -> Option<Vec<Event<'a>>> {
let (s, res) = match evt {
Event::Text(txt) => match state.take() {
Some(mut prev_txt) => {
prev_txt.push_str(&txt);
(Some(prev_txt), vec![])
}
None => (Some(txt.into_owned()), vec![]),
},
e => match state.take() {
Some(prev) => (None, vec![Event::Text(Cow::Owned(prev)), e]),
None => (None, vec![e]),
},
};
*state = s;
Some(res)
}
fn inline_tags<'a>(
(state, inline): &mut (Vec<Tag<'a>>, bool),
evt: Event<'a>,
) -> Option<Event<'a>> {
if *inline {
let new_evt = match evt {
Event::Start(t) => {
let tag = to_inline(t);
state.push(tag.clone());
Event::Start(tag)
}
Event::End(t) => match state.pop() {
Some(other) => Event::End(other),
None => Event::End(t),
},
e => e,
};
Some(new_evt)
} else {
Some(evt)
}
}
pub type MediaProcessor<'a> = Box<'a + Fn(i32) -> Option<(String, Option<String>)>>;
fn process_image<'a, 'b>(
evt: Event<'a>,
inline: bool,
processor: &Option<MediaProcessor<'b>>,
) -> Event<'a> {
if let Some(ref processor) = *processor {
match evt {
Event::Start(Tag::Image(id, title)) => {
if let Some((url, cw)) = id.parse::<i32>().ok().and_then(processor.as_ref()) {
if inline || cw.is_none() {
Event::Start(Tag::Image(Cow::Owned(url), title))
} else {
// there is a cw, and where are not inline
Event::Html(Cow::Owned(format!(
r#"<label for="postcontent-cw-{id}">
<input type="checkbox" id="postcontent-cw-{id}" checked="checked" class="cw-checkbox">
<span class="cw-container">
<span class="cw-text">
{cw}
</span>
<img src="{url}" alt=""#,
id = random_hex(),
cw = cw.unwrap(),
url = url
)))
}
} else {
Event::Start(Tag::Image(id, title))
}
}
Event::End(Tag::Image(id, title)) => {
if let Some((url, cw)) = id.parse::<i32>().ok().and_then(processor.as_ref()) {
if inline || cw.is_none() {
Event::End(Tag::Image(Cow::Owned(url), title))
} else {
Event::Html(Cow::Borrowed(
r#""/>
</span>
</label>"#,
))
}
} else {
Event::End(Tag::Image(id, title))
}
}
e => e,
}
} else {
evt
}
}
/// Returns (HTML, mentions, hashtags)
pub fn md_to_html(
pub fn md_to_html<'a>(
md: &str,
base_url: &str,
inline: bool,
media_processor: Option<MediaProcessor<'a>>,
) -> (String, HashSet<String>, HashSet<String>) {
let parser = Parser::new_ext(md, Options::all());
let (parser, mentions, hashtags): (Vec<Event>, Vec<String>, Vec<String>) = parser
.scan(None, |state: &mut Option<String>, evt| {
let (s, res) = match evt {
Event::Text(txt) => match state.take() {
Some(mut prev_txt) => {
prev_txt.push_str(&txt);
(Some(prev_txt), vec![])
}
None => (Some(txt.into_owned()), vec![]),
},
e => match state.take() {
Some(prev) => (None, vec![Event::Text(Cow::Owned(prev)), e]),
None => (None, vec![e]),
},
};
*state = s;
Some(res)
})
// Flatten text because pulldown_cmark break #hashtag in two individual text elements
.scan(None, flatten_text)
.flat_map(IntoIterator::into_iter)
.map(|evt| process_image(evt, inline, &media_processor))
// Ignore headings, images, and tables if inline = true
.scan(vec![], |state: &mut Vec<Tag>, evt| {
if inline {
let new_evt = match evt {
Event::Start(t) => {
let tag = to_inline(t);
state.push(tag.clone());
Event::Start(tag)
}
Event::End(t) => match state.pop() {
Some(other) => Event::End(other),
None => Event::End(t),
},
e => e,
};
Some(new_evt)
} else {
Some(evt)
}
})
.scan((vec![], inline), inline_tags)
.map(|evt| match evt {
Event::Text(txt) => {
let (evts, _, _, _, new_mentions, new_hashtags) = txt.chars().fold(
@@ -273,7 +337,7 @@ mod tests {
for (md, mentions) in tests {
assert_eq!(
md_to_html(md, "", false).1,
md_to_html(md, "", false, None).1,
mentions
.into_iter()
.map(|s| s.to_string())
@@ -298,7 +362,7 @@ mod tests {
for (md, mentions) in tests {
assert_eq!(
md_to_html(md, "", false).2,
md_to_html(md, "", false, None).2,
mentions
.into_iter()
.map(|s| s.to_string())
@@ -310,11 +374,11 @@ mod tests {
#[test]
fn test_inline() {
assert_eq!(
md_to_html("# Hello", "", false).0,
md_to_html("# Hello", "", false, None).0,
String::from("<h1>Hello</h1>\n")
);
assert_eq!(
md_to_html("# Hello", "", true).0,
md_to_html("# Hello", "", true, None).0,
String::from("<p>Hello</p>\n")
);
}