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:
parent
e6563e2c83
commit
bedbef97e8
1 changed files with 71 additions and 15 deletions
86
src/post.rs
86
src/post.rs
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue