1use mdbook_core::utils::fs;
2use mdbook_preprocessor::book::Book;
3use mdbook_preprocessor::errors::Result;
4use mdbook_preprocessor::{Preprocessor, PreprocessorContext};
5use serde::Deserialize;
6use std::path::Path;
7use tracing::info;
8
9#[derive(Debug, Deserialize)]
10#[serde(default)]
11struct PagetocConfig {
12 scroll_offset: i32,
13}
14
15impl Default for PagetocConfig {
16 fn default() -> Self {
17 Self { scroll_offset: 10 }
18 }
19}
20
21pub struct PagetocPreprocessor;
22
23impl Default for PagetocPreprocessor {
24 fn default() -> Self {
25 Self::new()
26 }
27}
28
29impl PagetocPreprocessor {
30 pub fn new() -> PagetocPreprocessor {
31 PagetocPreprocessor
32 }
33}
34
35impl Preprocessor for PagetocPreprocessor {
36 fn name(&self) -> &str {
37 "mdbook-pagetoc"
38 }
39
40 fn run(&self, ctx: &PreprocessorContext, book: Book) -> Result<Book> {
41 let html_config = ctx.config.html_config().unwrap_or_default();
42
43 let config: PagetocConfig = ctx
44 .config
45 .get("preprocessor.pagetoc")
46 .ok()
47 .flatten()
48 .unwrap_or_default();
49
50 let pagetoc_js = include_str!("pagetoc.js")
51 .replace("{{SCROLL_OFFSET}}", &config.scroll_offset.to_string());
52 let pagetoc_css = include_str!("pagetoc.css");
53
54 let theme_dir = ctx
55 .root
56 .join(html_config.theme.as_deref().unwrap_or(Path::new("theme")));
57
58 for (name, contents) in [
59 ("pagetoc.js", pagetoc_js.as_str()),
60 ("pagetoc.css", pagetoc_css),
61 ] {
62 let path = theme_dir.join(name);
63 if !path.exists() {
64 info!("Writing {}", path.display());
65 fs::write(&path, contents)?;
66 }
67 }
68 Ok(book)
69 }
70
71 fn supports_renderer(&self, renderer: &str) -> Result<bool> {
72 Ok(renderer == "html")
73 }
74}