use std::{fs, path::Path};
use anyhow::Context;
use bookyard_core::{BookyardConfig, build_catalog};
const SHELF_CSS: &str = include_str!("../../assets/shelf/style.css");
pub fn write_shelf(output_dir: &Path, config: &BookyardConfig) -> anyhow::Result<()> {
fs::create_dir_all(output_dir.join("assets"))
.with_context(|| format!("failed to create {}", output_dir.join("assets").display()))?;
fs::write(output_dir.join("assets/style.css"), SHELF_CSS)
.with_context(|| "failed to write shelf stylesheet")?;
fs::write(output_dir.join("index.html"), render_shelf(config))
.with_context(|| "failed to write shelf index")?;
let catalog = build_catalog(config);
fs::write(
output_dir.join("catalog.json"),
serde_json::to_string_pretty(&catalog)?,
)
.with_context(|| "failed to write shelf catalog")?;
Ok(())
}
pub fn render_shelf(config: &BookyardConfig) -> String {
let catalog = build_catalog(config);
let mut books = String::new();
for book in catalog.books {
let folders = if book.folders.is_empty() {
"Unsorted".to_owned()
} else {
book.folders.join(", ")
};
let tags = if book.tags.is_empty() {
String::new()
} else {
format!(
"<div class=\"tags\">{}</div>",
book.tags
.iter()
.map(|tag| format!("<span>{}</span>", escape_html(tag)))
.collect::<Vec<_>>()
.join("")
)
};
books.push_str(&format!(
"<article class=\"book-card\"><a href=\"{}\"><h2>{}</h2></a><p>{}</p>{}</article>",
escape_html(&book.href),
escape_html(&book.title),
escape_html(&folders),
tags,
));
}
format!(
r#"<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{title}</title>
<link rel="stylesheet" href="assets/style.css">
</head>
<body>
<main>
<header>
<p class="eyebrow">Bookyard</p>
<h1>{title}</h1>
</header>
<section class="grid">{books}</section>
</main>
</body>
</html>
"#,
title = escape_html(&config.workspace.title),
books = books,
)
}
fn escape_html(input: &str) -> String {
input
.replace('&', "&")
.replace('<', "<")
.replace('>', ">")
.replace('"', """)
}