Skip to main content

verso/export/
markdown.rs

1use crate::store::highlights::{AnchorStatus, Highlight};
2
3pub struct BookContext {
4    pub title: String,
5    pub author: Option<String>,
6    pub published: Option<String>,
7    pub progress_pct: Option<f32>,
8    pub source_path: String,
9    pub tags: Vec<String>,
10    pub exported_at: String,
11}
12
13pub fn render(ctx: &BookContext, highs: &[Highlight]) -> String {
14    let mut s = String::new();
15    s.push_str("---\n");
16    s.push_str(&format!("title: {}\n", ctx.title));
17    if let Some(a) = &ctx.author {
18        s.push_str(&format!("author: {a}\n"));
19    }
20    if let Some(p) = &ctx.published {
21        s.push_str(&format!("published: {p}\n"));
22    }
23    s.push_str(&format!("exported: {}\n", ctx.exported_at));
24    if let Some(p) = ctx.progress_pct {
25        s.push_str(&format!("progress: {p:.0}%\n"));
26    }
27    s.push_str(&format!("source: {}\n", ctx.source_path));
28    if !ctx.tags.is_empty() {
29        s.push_str(&format!("tags: [{}]\n", ctx.tags.join(", ")));
30    }
31    s.push_str("---\n\n");
32
33    let mut current_chapter: Option<String> = None;
34    for h in highs {
35        if h.chapter_title.as_deref() != current_chapter.as_deref() {
36            current_chapter = h.chapter_title.clone();
37            if let Some(ch) = &current_chapter {
38                s.push_str(&format!("## {ch}\n\n"));
39            }
40        }
41        let marker = if matches!(h.anchor_status, AnchorStatus::Drifted) {
42            " *(drifted)*"
43        } else if matches!(h.anchor_status, AnchorStatus::Lost) {
44            " *(lost)*"
45        } else {
46            ""
47        };
48        s.push_str(&format!("> {}{}\n\n", h.text.replace('\n', " "), marker));
49        if let Some(n) = &h.note {
50            s.push_str(&format!("**Note:** {n}\n\n"));
51        }
52        s.push_str("---\n\n");
53    }
54    s
55}