fix: list formatting

DESCRIPTION
    Medium's array based markup requires the view layer to peek into the
    next element to terminate the previous element's formatting rules.

    So a <ul> element's termination is specified by the appearance of an
    element that isn't a <ul>. This patch maintains state and lazily
    applies formatting rules when termination is detected.
This commit is contained in:
Aravinth Manivannan 2023-02-17 18:16:44 +05:30
parent e6563e2c83
commit bedbef97e8
Signed by: realaravinth
GPG key ID: AD9F0F08E855ED88

View file

@ -26,6 +26,20 @@ enum PostitionType {
End, End,
} }
struct ListState {
in_uli: bool,
in_oli: bool,
}
impl Default for ListState {
fn default() -> Self {
Self {
in_uli: false,
in_oli: false,
}
}
}
struct Markup<'a, 'b> { struct Markup<'a, 'b> {
markup: &'a GetPostPostContentBodyModelParagraphsMarkups, markup: &'a GetPostPostContentBodyModelParagraphsMarkups,
p: &'a GetPostPostContentBodyModelParagraphs, p: &'a GetPostPostContentBodyModelParagraphs,
@ -38,9 +52,10 @@ impl<'a, 'b> Markup<'a, 'b> {
p: &GetPostPostContentBodyModelParagraphs, p: &GetPostPostContentBodyModelParagraphs,
gists: &'b Option<Vec<(String, crate::data::GistContent)>>, gists: &'b Option<Vec<(String, crate::data::GistContent)>>,
pindex: usize, pindex: usize,
in_oli: &mut bool, state: &mut ListState,
) -> String { ) -> String {
if p.type_ == "IMG" { let list = Self::list_close(p, state);
let resp = if p.type_ == "IMG" {
let metadata = p.metadata.as_ref().unwrap(); let metadata = p.metadata.as_ref().unwrap();
format!( format!(
r#"<figure><img width="{}" src="{}" /> <figcaption>"#, r#"<figure><img width="{}" src="{}" /> <figcaption>"#,
@ -104,13 +119,20 @@ impl<'a, 'b> Markup<'a, 'b> {
} else { } else {
format!(r#"<iframe src="{src}" frameborder="0">"#) format!(r#"<iframe src="{src}" frameborder="0">"#)
} }
} else if p.type_ == "OLI" { } else if p.type_ == "ULI" {
if *in_oli { if state.in_uli {
"<li>".into() "<li>".into()
} else { } else {
*in_oli = true; state.in_uli = true;
"<ul><li>".into() "<ul><li>".into()
} }
} else if p.type_ == "OLI" {
if state.in_oli {
"<li>".into()
} else {
state.in_oli = true;
"<ol><li>".into()
}
} else { } else {
log::info!("Unknown type"); log::info!("Unknown type");
r#" r#"
@ -127,10 +149,19 @@ impl<'a, 'b> Markup<'a, 'b> {
</p> </p>
<span>"# <span>"#
.into() .into()
};
match list {
Some(list) => format!("{list}{resp}"),
None => resp,
} }
} }
fn end(p: &GetPostPostContentBodyModelParagraphs, pindex: usize, in_oli: &mut bool) -> String { fn end(
p: &GetPostPostContentBodyModelParagraphs,
pindex: usize,
state: &mut ListState,
) -> String {
let resp: String = if p.type_ == "IMG" { let resp: String = if p.type_ == "IMG" {
"</figcaption></figure>".into() "</figcaption></figure>".into()
} else if p.type_ == "P" { } else if p.type_ == "P" {
@ -170,14 +201,21 @@ impl<'a, 'b> Markup<'a, 'b> {
} else { } else {
"</iframe>".into() "</iframe>".into()
} }
} else if p.type_ == "OLI" { } else if p.type_ == "OLI" || p.type_ == "ULI" {
"</li>".into() "</li>".into()
} else { } else {
"</span>".into() "</span>".into()
}; };
if *in_oli { if state.in_oli {
if p.type_ != "OLL" { if p.type_ != "OLI" {
*in_oli = false; state.in_oli = false;
format!("</ol>{resp}")
} else {
resp
}
} else if state.in_uli {
if p.type_ != "ULI" {
state.in_uli = false;
format!("</ul>{resp}") format!("</ul>{resp}")
} else { } else {
resp resp
@ -187,6 +225,25 @@ impl<'a, 'b> Markup<'a, 'b> {
} }
} }
fn list_close(
p: &GetPostPostContentBodyModelParagraphs,
state: &mut ListState,
) -> Option<String> {
if state.in_oli {
if p.type_ != "OLI" {
state.in_oli = false;
return Some(format!("</ol>"));
}
};
if state.in_uli {
if p.type_ != "ULI" {
state.in_uli = false;
return Some(format!("</ul>"));
}
};
None
}
fn apply_markup(&self, pindex: usize) -> String { fn apply_markup(&self, pindex: usize) -> String {
if self.markup.type_ == "A" { if self.markup.type_ == "A" {
if let Some(anchor_type) = &self.markup.anchor_type { if let Some(anchor_type) = &self.markup.anchor_type {
@ -295,6 +352,7 @@ pub fn apply_markup<'b>(
gists: &'b Option<Vec<(String, crate::data::GistContent)>>, gists: &'b Option<Vec<(String, crate::data::GistContent)>>,
) -> Vec<String> { ) -> Vec<String> {
let mut paragraphs: Vec<String> = Vec::with_capacity(data.content.body_model.paragraphs.len()); let mut paragraphs: Vec<String> = Vec::with_capacity(data.content.body_model.paragraphs.len());
let mut state = ListState::default();
for (pindex, p) in data.content.body_model.paragraphs.iter().enumerate() { for (pindex, p) in data.content.body_model.paragraphs.iter().enumerate() {
let mut pos = PositionMap::default(); let mut pos = PositionMap::default();
if p.type_ == "H3" && pindex == 0 { if p.type_ == "H3" && pindex == 0 {
@ -333,8 +391,7 @@ pub fn apply_markup<'b>(
} }
let mut content = String::with_capacity(p.text.len()); let mut content = String::with_capacity(p.text.len());
let mut in_oli = false; content += &Markup::start(&p, &gists, pindex, &mut state);
content += &Markup::start(&p, &gists, pindex, &mut in_oli);
pos.arr.sort(); pos.arr.sort();
if let Some(first) = pos.arr.get(0) { if let Some(first) = pos.arr.get(0) {
//content += p.text.substring(cur, *first as usize); //content += p.text.substring(cur, *first as usize);
@ -351,18 +408,17 @@ pub fn apply_markup<'b>(
// } // }
let pos_markups = pos.map.get(point).unwrap(); let pos_markups = pos.map.get(point).unwrap();
for m in pos_markups.iter() { for m in pos_markups.iter() {
// println!("{}", &m.apply_markup(pindex));
content += &m.apply_markup(pindex); content += &m.apply_markup(pindex);
} }
cur = incr_cur(cur, *point); cur = incr_cur(cur, *point);
} }
log::debug!("LAST"); log::debug!("LAST");
content += p.text.slice(cur..); content += p.text.slice(cur..);
content += &Markup::end(&p, pindex, &mut in_oli); content += &Markup::end(&p, pindex, &mut state);
} else { } else {
log::debug!("LAST WITH NO MARKUP"); log::debug!("LAST WITH NO MARKUP");
content += p.text.slice(cur..); content += p.text.slice(cur..);
content += &Markup::end(&p, pindex, &mut in_oli); content += &Markup::end(&p, pindex, &mut state);
} }
paragraphs.push(content); paragraphs.push(content);
} }