Make anchors aware of the nodes they point to

This commit is contained in:
Juno Takano 2026-01-11 08:00:35 -03:00
commit 3fa399c317
31 changed files with 366 additions and 230 deletions

View file

@ -7,6 +7,7 @@ use crate::{
pub struct Anchor {
text: String,
destination: Option<String>,
node_id: Option<String>,
node: Option<Node>,
leading: bool,
balanced: bool,
@ -18,6 +19,7 @@ impl Anchor {
text: &str,
destination: &str,
node: Option<Node>,
node_id: Option<String>,
leading: bool,
external: bool,
balanced: bool,
@ -26,6 +28,7 @@ impl Anchor {
text: text.to_owned(),
destination: Some(String::from(destination)),
node,
node_id,
leading,
external,
balanced,
@ -76,6 +79,14 @@ impl Anchor {
self.leading = leading;
}
pub fn set_node(&mut self, node: &Node) {
self.node = Some(node.to_owned());
}
pub fn node_id(&self) -> Option<String> {
self.node_id.clone()
}
fn route(&mut self) {
self.destination = if let Some(destination) = self.destination.clone() {
if destination.contains(":") || destination.contains("/") {
@ -83,8 +94,10 @@ impl Anchor {
} else if destination.is_empty() && self.text.is_empty() {
None
} else if destination.is_empty() {
self.node_id = Some(self.text.clone());
Some(format!("/node/{}", self.text))
} else {
self.node_id = self.destination.clone();
Some(format!("/node/{destination}"))
}
} else {
@ -111,7 +124,30 @@ impl Parseable for Anchor {
)
};
format!(r#"<a href="{}">{}</a>"#, destination, &self.text)
let summary = if let Some(node) = self.node.clone() {
node.summary
} else {
String::default()
};
let classes = if self.node.is_some() {
String::from(r#"class="attached""#)
} else if !self.external {
String::from(r#"class="detached""#)
} else if self.external {
String::from(r#"class="external""#)
} else {
String::default()
};
format!(
r#"<a {classes} title="{summary}" href="{}">{}</a>"#,
destination, self.text,
)
}
fn flatten(&self) -> String {
self.text.clone()
}
}
@ -167,7 +203,7 @@ mod tests {
anchor.set_destination(Some("AnchorDest"));
assert_eq!(
anchor.render(),
r#"<a href="/node/AnchorDest">AnchorText</a>"#
r#"<a class="detached" title="" href="/node/AnchorDest">AnchorText</a>"#
);
}
@ -190,20 +226,20 @@ mod tests {
fn token_display() {
let mut anchor = Anchor::default();
assert_eq!(
format!("{}", Token::Anchor(anchor.clone())),
format!("{}", Token::Anchor(Box::new(anchor.clone()))),
"Tk:Anchor <empty> -> <unknown>",
);
anchor.text = String::from("FsJAt RTggA");
assert_eq!(
format!("{}", Token::Anchor(anchor.clone())),
format!("{}", Token::Anchor(Box::new(anchor.clone()))),
"Tk:Anchor 'FsJAt RTggA' -> <unknown>",
);
anchor.text = String::from("wPVo1 0OmYm");
anchor.destination = Some(String::from("M1UEp 1gbfr"));
assert_eq!(
format!("{}", Token::Anchor(anchor.clone())),
format!("{}", Token::Anchor(Box::new(anchor.clone()))),
r#"Tk:Anchor 'wPVo1 0OmYm' -> "M1UEp 1gbfr""#,
);
@ -212,7 +248,7 @@ mod tests {
anchor.external = true;
assert_eq!(
format!("{}", Token::Anchor(anchor.clone())),
format!("{}", Token::Anchor(Box::new(anchor.clone()))),
"Tk:Anchor 'wPVo1 0OmYm' -> \"M1UEp 1gbfr\" \
+Leading +Balanced +External",
);
@ -230,7 +266,10 @@ mod tests {
let mut anchor = Anchor::default();
anchor.set_text("BSThI");
anchor.set_destination(Some(""));
assert_eq!(anchor.render(), r#"<a href="/node/BSThI">BSThI</a>"#);
assert_eq!(
anchor.render(),
r#"<a class="detached" title="" href="/node/BSThI">BSThI</a>"#
);
}
#[test]

View file

@ -29,6 +29,10 @@ impl Parseable for Bold {
String::from("</strong>")
}
}
fn flatten(&self) -> String {
String::default()
}
}
impl std::fmt::Display for Bold {

View file

@ -33,6 +33,10 @@ impl Parseable for CheckBox {
let toggle = if self.checked { " checked " } else { "" };
format!(r#"<input type="checkbox"{toggle}/>"#)
}
fn flatten(&self) -> String {
String::default()
}
}
impl std::fmt::Display for CheckBox {

View file

@ -29,6 +29,10 @@ impl Parseable for Code {
String::from("</code>")
}
}
fn flatten(&self) -> String {
String::default()
}
}
impl std::fmt::Display for Code {

View file

@ -112,6 +112,10 @@ impl Parseable for Header {
panic!("Attempt to render a header tag while open state is unknown")
}
}
fn flatten(&self) -> String {
String::default()
}
}
impl std::fmt::Display for Header {

View file

@ -18,6 +18,10 @@ impl Parseable for Item {
fn render(&self) -> String {
panic!("Items should only be rendered by a list's render method")
}
fn flatten(&self) -> String {
String::default()
}
}
impl Item {

View file

@ -17,6 +17,10 @@ impl Parseable for LineBreak {
fn render(&self) -> String {
"\n".to_owned()
}
fn flatten(&self) -> String {
String::from('\n')
}
}
impl std::fmt::Display for LineBreak {

View file

@ -51,6 +51,10 @@ impl Parseable for List {
format!("\n<{tag}>\n{output}</{tag}>\n\n")
}
fn flatten(&self) -> String {
format!("[List: {} items]", self.items.len())
}
}
impl List {

View file

@ -19,6 +19,10 @@ impl Parseable for Literal {
fn render(&self) -> String {
self.text.clone()
}
fn flatten(&self) -> String {
self.text.clone()
}
}
impl std::fmt::Display for Literal {

View file

@ -29,6 +29,10 @@ impl Parseable for Oblique {
String::from("</em>")
}
}
fn flatten(&self) -> String {
String::default()
}
}
impl std::fmt::Display for Oblique {

View file

@ -38,6 +38,10 @@ impl Parseable for Paragraph {
)
}
}
fn flatten(&self) -> String {
String::default()
}
}
impl std::fmt::Display for Paragraph {

View file

@ -46,6 +46,10 @@ impl Parseable for PreFormat {
)
}
}
fn flatten(&self) -> String {
String::default()
}
}
#[cfg(test)]

View file

@ -26,6 +26,10 @@ impl Parseable for Strike {
let tag = if self.open { "<s>" } else { "</s>" };
String::from(tag)
}
fn flatten(&self) -> String {
String::default()
}
}
impl std::fmt::Display for Strike {

View file

@ -29,6 +29,10 @@ impl Parseable for Underline {
String::from("</u>")
}
}
fn flatten(&self) -> String {
String::default()
}
}
impl std::fmt::Display for Underline {