Redirect to a 404 page when serving translated
We can't redirect in warp based on the URL, so redirect to the default language's 404 page instead. See: https://github.com/seanmonstar/warp/issues/171
This commit is contained in:
parent
85ab4d39cd
commit
282fdaa3ac
@ -18,27 +18,37 @@ pub fn load_book<P: AsRef<Path>>(
|
|||||||
if cfg.language.has_localized_dir_structure() {
|
if cfg.language.has_localized_dir_structure() {
|
||||||
match build_opts.language_ident {
|
match build_opts.language_ident {
|
||||||
// Build a single book's translation.
|
// Build a single book's translation.
|
||||||
Some(_) => Ok(LoadedBook::Single(load_single_book_translation(&root_dir, cfg, &build_opts.language_ident)?)),
|
Some(_) => Ok(LoadedBook::Single(load_single_book_translation(
|
||||||
|
&root_dir,
|
||||||
|
cfg,
|
||||||
|
&build_opts.language_ident,
|
||||||
|
)?)),
|
||||||
// Build all available translations at once.
|
// Build all available translations at once.
|
||||||
None => {
|
None => {
|
||||||
let mut translations = HashMap::new();
|
let mut translations = HashMap::new();
|
||||||
for (lang_ident, _) in cfg.language.0.iter() {
|
for (lang_ident, _) in cfg.language.0.iter() {
|
||||||
let book = load_single_book_translation(&root_dir, cfg, &Some(lang_ident.clone()))?;
|
let book =
|
||||||
|
load_single_book_translation(&root_dir, cfg, &Some(lang_ident.clone()))?;
|
||||||
translations.insert(lang_ident.clone(), book);
|
translations.insert(lang_ident.clone(), book);
|
||||||
}
|
}
|
||||||
Ok(LoadedBook::Localized(LocalizedBooks(translations)))
|
Ok(LoadedBook::Localized(LocalizedBooks(translations)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Ok(LoadedBook::Single(load_single_book_translation(&root_dir, cfg, &None)?))
|
Ok(LoadedBook::Single(load_single_book_translation(
|
||||||
|
&root_dir, cfg, &None,
|
||||||
|
)?))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn load_single_book_translation<P: AsRef<Path>>(root_dir: P, cfg: &Config, language_ident: &Option<String>) -> Result<Book> {
|
fn load_single_book_translation<P: AsRef<Path>>(
|
||||||
let localized_src_dir = root_dir.as_ref().join(
|
root_dir: P,
|
||||||
cfg.get_localized_src_path(language_ident.as_ref())
|
cfg: &Config,
|
||||||
.unwrap(),
|
language_ident: &Option<String>,
|
||||||
);
|
) -> Result<Book> {
|
||||||
|
let localized_src_dir = root_dir
|
||||||
|
.as_ref()
|
||||||
|
.join(cfg.get_localized_src_path(language_ident.as_ref()).unwrap());
|
||||||
let fallback_src_dir = root_dir.as_ref().join(cfg.get_fallback_src_path());
|
let fallback_src_dir = root_dir.as_ref().join(cfg.get_fallback_src_path());
|
||||||
|
|
||||||
let summary_md = localized_src_dir.join("SUMMARY.md");
|
let summary_md = localized_src_dir.join("SUMMARY.md");
|
||||||
@ -172,9 +182,7 @@ impl LocalizedBooks {
|
|||||||
items.extend(book.iter().items);
|
items.extend(book.iter().items);
|
||||||
}
|
}
|
||||||
|
|
||||||
BookItems {
|
BookItems { items: items }
|
||||||
items: items
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Recursively apply a closure to each item in the book, allowing you to
|
/// Recursively apply a closure to each item in the book, allowing you to
|
||||||
@ -239,7 +247,7 @@ impl LoadedBook {
|
|||||||
pub fn first(&self) -> &Book {
|
pub fn first(&self) -> &Book {
|
||||||
match self {
|
match self {
|
||||||
LoadedBook::Localized(books) => books.0.iter().next().unwrap().1,
|
LoadedBook::Localized(books) => books.0.iter().next().unwrap().1,
|
||||||
LoadedBook::Single(book) => &book
|
LoadedBook::Single(book) => &book,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -617,7 +625,7 @@ more text.
|
|||||||
Vec::new(),
|
Vec::new(),
|
||||||
&cfg,
|
&cfg,
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(got, should_be);
|
assert_eq!(got, should_be);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -10,15 +10,15 @@ mod book;
|
|||||||
mod init;
|
mod init;
|
||||||
mod summary;
|
mod summary;
|
||||||
|
|
||||||
pub use self::book::{load_book, BookItem, BookItems, Chapter, Book, LocalizedBooks, LoadedBook};
|
pub use self::book::{load_book, Book, BookItem, BookItems, Chapter, LoadedBook, LocalizedBooks};
|
||||||
pub use self::init::BookBuilder;
|
pub use self::init::BookBuilder;
|
||||||
pub use self::summary::{parse_summary, Link, SectionNumber, Summary, SummaryItem};
|
pub use self::summary::{parse_summary, Link, SectionNumber, Summary, SummaryItem};
|
||||||
|
|
||||||
|
use std::collections::HashMap;
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
use std::string::ToString;
|
use std::string::ToString;
|
||||||
use std::collections::HashMap;
|
|
||||||
use tempfile::Builder as TempFileBuilder;
|
use tempfile::Builder as TempFileBuilder;
|
||||||
use tempfile::TempDir;
|
use tempfile::TempDir;
|
||||||
use toml::Value;
|
use toml::Value;
|
||||||
@ -133,8 +133,12 @@ impl MDBook {
|
|||||||
.unwrap(),
|
.unwrap(),
|
||||||
);
|
);
|
||||||
let fallback_src_dir = root.join(config.get_fallback_src_path());
|
let fallback_src_dir = root.join(config.get_fallback_src_path());
|
||||||
let book =
|
let book = LoadedBook::Single(book::load_book_from_disk(
|
||||||
LoadedBook::Single(book::load_book_from_disk(&summary, localized_src_dir, fallback_src_dir, &config)?);
|
&summary,
|
||||||
|
localized_src_dir,
|
||||||
|
fallback_src_dir,
|
||||||
|
&config,
|
||||||
|
)?);
|
||||||
|
|
||||||
let renderers = determine_renderers(&config);
|
let renderers = determine_renderers(&config);
|
||||||
let preprocessors = determine_preprocessors(&config)?;
|
let preprocessors = determine_preprocessors(&config)?;
|
||||||
@ -223,13 +227,16 @@ impl MDBook {
|
|||||||
let mut new_books = HashMap::new();
|
let mut new_books = HashMap::new();
|
||||||
|
|
||||||
for (ident, book) in books.0.iter() {
|
for (ident, book) in books.0.iter() {
|
||||||
let preprocessed_book = self.preprocess(&preprocess_ctx, renderer, book.clone())?;
|
let preprocessed_book =
|
||||||
|
self.preprocess(&preprocess_ctx, renderer, book.clone())?;
|
||||||
new_books.insert(ident.clone(), preprocessed_book);
|
new_books.insert(ident.clone(), preprocessed_book);
|
||||||
}
|
}
|
||||||
|
|
||||||
LoadedBook::Localized(LocalizedBooks(new_books))
|
LoadedBook::Localized(LocalizedBooks(new_books))
|
||||||
},
|
}
|
||||||
LoadedBook::Single(ref book) => LoadedBook::Single(self.preprocess(&preprocess_ctx, renderer, book.clone())?),
|
LoadedBook::Single(ref book) => {
|
||||||
|
LoadedBook::Single(self.preprocess(&preprocess_ctx, renderer, book.clone())?)
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let name = renderer.name();
|
let name = renderer.name();
|
||||||
|
@ -92,7 +92,7 @@ pub fn execute(args: &ArgMatches) -> Result<()> {
|
|||||||
Some(lang_ident) => Some(lang_ident.clone()),
|
Some(lang_ident) => Some(lang_ident.clone()),
|
||||||
// If not, it will be at the root.
|
// If not, it will be at the root.
|
||||||
None => None,
|
None => None,
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
let sockaddr: SocketAddr = address
|
let sockaddr: SocketAddr = address
|
||||||
@ -177,9 +177,6 @@ async fn serve(
|
|||||||
});
|
});
|
||||||
// A warp Filter that serves from the filesystem.
|
// A warp Filter that serves from the filesystem.
|
||||||
let book_route = warp::fs::dir(build_dir.clone());
|
let book_route = warp::fs::dir(build_dir.clone());
|
||||||
// The fallback route for 404 errors
|
|
||||||
let fallback_route = warp::fs::file(build_dir.join(file_404))
|
|
||||||
.map(|reply| warp::reply::with_status(reply, warp::http::StatusCode::NOT_FOUND));
|
|
||||||
|
|
||||||
std::panic::set_hook(Box::new(move |panic_info| {
|
std::panic::set_hook(Box::new(move |panic_info| {
|
||||||
// exit if serve panics
|
// exit if serve panics
|
||||||
@ -189,13 +186,31 @@ async fn serve(
|
|||||||
|
|
||||||
if let Some(lang_ident) = language {
|
if let Some(lang_ident) = language {
|
||||||
// Redirect root to the default translation directory, if serving a localized book.
|
// Redirect root to the default translation directory, if serving a localized book.
|
||||||
// BUG: This can't be `/{lang_ident}`, or the static assets won't get loaded.
|
// NOTE: This can't be `/{lang_ident}`, or the static assets won't get loaded.
|
||||||
let index_for_language = format!("/{}/index.html", lang_ident).parse::<Uri>().unwrap();
|
// BUG: Redirects get cached if you change the --language parameter,
|
||||||
let redirect_to_index = warp::path::end().map(move || warp::redirect(index_for_language.clone()));
|
// meaning you'll get a 404 unless you disable cache in the developer tools.
|
||||||
let routes = livereload.or(redirect_to_index).or(book_route).or(fallback_route);
|
let index_for_language = format!("/{}/index.html", lang_ident)
|
||||||
|
.parse::<Uri>()
|
||||||
|
.unwrap();
|
||||||
|
let redirect_to_index =
|
||||||
|
warp::path::end().map(move || warp::redirect(index_for_language.clone()));
|
||||||
|
|
||||||
|
// BUG: It is not possible to conditionally redirect to the correct 404
|
||||||
|
// page depending on the URL in warp, so just redirect to the one in the
|
||||||
|
// default language.
|
||||||
|
// See: https://github.com/seanmonstar/warp/issues/171
|
||||||
|
let fallback_route = warp::fs::file(build_dir.join(lang_ident).join(file_404))
|
||||||
|
.map(|reply| warp::reply::with_status(reply, warp::http::StatusCode::NOT_FOUND));
|
||||||
|
|
||||||
|
let routes = livereload
|
||||||
|
.or(redirect_to_index)
|
||||||
|
.or(book_route)
|
||||||
|
.or(fallback_route);
|
||||||
warp::serve(routes).run(address).await;
|
warp::serve(routes).run(address).await;
|
||||||
}
|
} else {
|
||||||
else {
|
// The fallback route for 404 errors
|
||||||
|
let fallback_route = warp::fs::file(build_dir.join(file_404))
|
||||||
|
.map(|reply| warp::reply::with_status(reply, warp::http::StatusCode::NOT_FOUND));
|
||||||
let routes = livereload.or(book_route).or(fallback_route);
|
let routes = livereload.or(book_route).or(fallback_route);
|
||||||
warp::serve(routes).run(address).await;
|
warp::serve(routes).run(address).await;
|
||||||
};
|
};
|
||||||
|
@ -199,7 +199,8 @@ mod tests {
|
|||||||
);
|
);
|
||||||
|
|
||||||
let mut buffer = Vec::new();
|
let mut buffer = Vec::new();
|
||||||
cmd.write_input(&mut buffer, &md.book.first(), &ctx).unwrap();
|
cmd.write_input(&mut buffer, &md.book.first(), &ctx)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
let (got_ctx, got_book) = CmdPreprocessor::parse_input(buffer.as_slice()).unwrap();
|
let (got_ctx, got_book) = CmdPreprocessor::parse_input(buffer.as_slice()).unwrap();
|
||||||
|
|
||||||
|
@ -62,31 +62,49 @@ impl HtmlHandlebars {
|
|||||||
let mut path = src_dir.clone();
|
let mut path = src_dir.clone();
|
||||||
path.push(lang_ident);
|
path.push(lang_ident);
|
||||||
path
|
path
|
||||||
},
|
}
|
||||||
// `src_dir` is where index.html and the other extra files
|
// `src_dir` is where index.html and the other extra files
|
||||||
// are, so use that.
|
// are, so use that.
|
||||||
None => src_dir.clone()
|
None => src_dir.clone(),
|
||||||
};
|
};
|
||||||
self.render_book(ctx, &book, src_dir, &extra_file_dir, &ctx.destination, &ctx.config.build.build_dir, html_config, handlebars, theme)?;
|
self.render_book(
|
||||||
|
ctx,
|
||||||
|
&book,
|
||||||
|
src_dir,
|
||||||
|
&extra_file_dir,
|
||||||
|
&ctx.destination,
|
||||||
|
&ctx.config.build.build_dir,
|
||||||
|
html_config,
|
||||||
|
handlebars,
|
||||||
|
theme,
|
||||||
|
)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_book<'a>(&self,
|
fn render_book<'a>(
|
||||||
ctx: &RenderContext,
|
&self,
|
||||||
book: &Book,
|
ctx: &RenderContext,
|
||||||
src_dir: &PathBuf,
|
book: &Book,
|
||||||
extra_file_dir: &PathBuf,
|
src_dir: &PathBuf,
|
||||||
destination: &PathBuf,
|
extra_file_dir: &PathBuf,
|
||||||
build_dir: &PathBuf,
|
destination: &PathBuf,
|
||||||
html_config: &HtmlConfig,
|
build_dir: &PathBuf,
|
||||||
handlebars: &mut Handlebars<'a>,
|
html_config: &HtmlConfig,
|
||||||
theme: &Theme,
|
handlebars: &mut Handlebars<'a>,
|
||||||
|
theme: &Theme,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let build_dir = ctx.root.join(build_dir);
|
let build_dir = ctx.root.join(build_dir);
|
||||||
let mut data = make_data(&ctx.root, &book, &ctx.book, &ctx.config, &html_config, &theme)?;
|
let mut data = make_data(
|
||||||
|
&ctx.root,
|
||||||
|
&book,
|
||||||
|
&ctx.book,
|
||||||
|
&ctx.config,
|
||||||
|
&html_config,
|
||||||
|
&theme,
|
||||||
|
)?;
|
||||||
|
|
||||||
// Print version
|
// Print version
|
||||||
let mut print_content = String::new();
|
let mut print_content = String::new();
|
||||||
@ -110,7 +128,14 @@ impl HtmlHandlebars {
|
|||||||
|
|
||||||
// Render 404 page
|
// Render 404 page
|
||||||
if html_config.input_404 != Some("".to_string()) {
|
if html_config.input_404 != Some("".to_string()) {
|
||||||
self.render_404(ctx, &html_config, src_dir, destination, handlebars, &mut data)?;
|
self.render_404(
|
||||||
|
ctx,
|
||||||
|
&html_config,
|
||||||
|
src_dir,
|
||||||
|
destination,
|
||||||
|
handlebars,
|
||||||
|
&mut data,
|
||||||
|
)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Print version
|
// Print version
|
||||||
@ -123,7 +148,8 @@ impl HtmlHandlebars {
|
|||||||
debug!("Render template");
|
debug!("Render template");
|
||||||
let rendered = handlebars.render("index", &data)?;
|
let rendered = handlebars.render("index", &data)?;
|
||||||
|
|
||||||
let rendered = self.post_process(rendered, &html_config.playground, ctx.config.rust.edition);
|
let rendered =
|
||||||
|
self.post_process(rendered, &html_config.playground, ctx.config.rust.edition);
|
||||||
|
|
||||||
utils::fs::write_file(&destination, "print.html", rendered.as_bytes())?;
|
utils::fs::write_file(&destination, "print.html", rendered.as_bytes())?;
|
||||||
debug!("Creating print.html ✓");
|
debug!("Creating print.html ✓");
|
||||||
@ -147,12 +173,17 @@ impl HtmlHandlebars {
|
|||||||
.context("Unable to emit redirects")?;
|
.context("Unable to emit redirects")?;
|
||||||
|
|
||||||
// Copy all remaining files, avoid a recursive copy from/to the book build dir
|
// Copy all remaining files, avoid a recursive copy from/to the book build dir
|
||||||
utils::fs::copy_files_except_ext(&extra_file_dir, &destination, true, Some(&build_dir), &["md"])?;
|
utils::fs::copy_files_except_ext(
|
||||||
|
&extra_file_dir,
|
||||||
|
&destination,
|
||||||
|
true,
|
||||||
|
Some(&build_dir),
|
||||||
|
&["md"],
|
||||||
|
)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fn render_item(
|
fn render_item(
|
||||||
&self,
|
&self,
|
||||||
item: &BookItem,
|
item: &BookItem,
|
||||||
@ -229,7 +260,7 @@ impl HtmlHandlebars {
|
|||||||
);
|
);
|
||||||
if let Some(ref section) = ch.number {
|
if let Some(ref section) = ch.number {
|
||||||
ctx.data
|
ctx.data
|
||||||
.insert("section".to_owned(), json!(section.to_string()));
|
.insert("section".to_owned(), json!(section.to_string()));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Render the handlebars template with the data
|
// Render the handlebars template with the data
|
||||||
@ -461,7 +492,10 @@ impl HtmlHandlebars {
|
|||||||
handlebars.register_helper("previous", Box::new(helpers::navigation::previous));
|
handlebars.register_helper("previous", Box::new(helpers::navigation::previous));
|
||||||
handlebars.register_helper("next", Box::new(helpers::navigation::next));
|
handlebars.register_helper("next", Box::new(helpers::navigation::next));
|
||||||
handlebars.register_helper("theme_option", Box::new(helpers::theme::theme_option));
|
handlebars.register_helper("theme_option", Box::new(helpers::theme::theme_option));
|
||||||
handlebars.register_helper("language_option", Box::new(helpers::language::language_option));
|
handlebars.register_helper(
|
||||||
|
"language_option",
|
||||||
|
Box::new(helpers::language::language_option),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Copy across any additional CSS and JavaScript files which the book
|
/// Copy across any additional CSS and JavaScript files which the book
|
||||||
@ -568,7 +602,7 @@ impl HtmlHandlebars {
|
|||||||
fn maybe_wrong_theme_dir(dir: &Path) -> Result<bool> {
|
fn maybe_wrong_theme_dir(dir: &Path) -> Result<bool> {
|
||||||
fn entry_is_maybe_book_file(entry: fs::DirEntry) -> Result<bool> {
|
fn entry_is_maybe_book_file(entry: fs::DirEntry) -> Result<bool> {
|
||||||
Ok(entry.file_type()?.is_file()
|
Ok(entry.file_type()?.is_file()
|
||||||
&& entry.path().extension().map_or(false, |ext| ext == "md"))
|
&& entry.path().extension().map_or(false, |ext| ext == "md"))
|
||||||
}
|
}
|
||||||
|
|
||||||
if dir.is_dir() {
|
if dir.is_dir() {
|
||||||
@ -842,7 +876,7 @@ fn make_data(
|
|||||||
languages.sort();
|
languages.sort();
|
||||||
data.insert("languages".to_owned(), json!(languages));
|
data.insert("languages".to_owned(), json!(languages));
|
||||||
data.insert("language_config".to_owned(), json!(config.language.clone()));
|
data.insert("language_config".to_owned(), json!(config.language.clone()));
|
||||||
},
|
}
|
||||||
LoadedBook::Single(_) => {
|
LoadedBook::Single(_) => {
|
||||||
data.insert("languages_enabled".to_owned(), json!(false));
|
data.insert("languages_enabled".to_owned(), json!(false));
|
||||||
}
|
}
|
||||||
@ -1012,7 +1046,7 @@ fn add_playground_pre(
|
|||||||
"\n# #![allow(unused)]\n{}#fn main() {{\n{}#}}",
|
"\n# #![allow(unused)]\n{}#fn main() {{\n{}#}}",
|
||||||
attrs, code
|
attrs, code
|
||||||
)
|
)
|
||||||
.into()
|
.into()
|
||||||
};
|
};
|
||||||
hide_lines(&content)
|
hide_lines(&content)
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
|
use crate::config::LanguageConfig;
|
||||||
use handlebars::{Context, Handlebars, Helper, Output, RenderContext, RenderError};
|
use handlebars::{Context, Handlebars, Helper, Output, RenderContext, RenderError};
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use crate::config::LanguageConfig;
|
|
||||||
|
|
||||||
pub fn language_option(
|
pub fn language_option(
|
||||||
h: &Helper<'_, '_>,
|
h: &Helper<'_, '_>,
|
||||||
@ -22,10 +22,10 @@ pub fn language_option(
|
|||||||
|
|
||||||
let current_path = rc
|
let current_path = rc
|
||||||
.evaluate(ctx, "@root/path")?
|
.evaluate(ctx, "@root/path")?
|
||||||
.as_json()
|
.as_json()
|
||||||
.as_str()
|
.as_str()
|
||||||
.ok_or_else(|| RenderError::new("Type error for `path`, string expected"))?
|
.ok_or_else(|| RenderError::new("Type error for `path`, string expected"))?
|
||||||
.replace("\"", "");
|
.replace("\"", "");
|
||||||
|
|
||||||
let rendered_path = Path::new(¤t_path)
|
let rendered_path = Path::new(¤t_path)
|
||||||
.with_extension("html")
|
.with_extension("html")
|
||||||
@ -35,14 +35,15 @@ pub fn language_option(
|
|||||||
|
|
||||||
let path_to_root = rc
|
let path_to_root = rc
|
||||||
.evaluate(ctx, "@root/path_to_root")?
|
.evaluate(ctx, "@root/path_to_root")?
|
||||||
.as_json()
|
.as_json()
|
||||||
.as_str()
|
.as_str()
|
||||||
.ok_or_else(|| RenderError::new("Type error for `path_to_root`, string expected"))?
|
.ok_or_else(|| RenderError::new("Type error for `path_to_root`, string expected"))?
|
||||||
.to_string();
|
.to_string();
|
||||||
|
|
||||||
let language = languages.0.get(param).ok_or_else(|| {
|
let language = languages
|
||||||
RenderError::new(format!("Unknown language identifier '{}'", param))
|
.0
|
||||||
})?;
|
.get(param)
|
||||||
|
.ok_or_else(|| RenderError::new(format!("Unknown language identifier '{}'", param)))?;
|
||||||
|
|
||||||
let mut href = String::new();
|
let mut href = String::new();
|
||||||
href.push_str(&path_to_root);
|
href.push_str(&path_to_root);
|
||||||
@ -51,7 +52,10 @@ pub fn language_option(
|
|||||||
href.push_str("/");
|
href.push_str("/");
|
||||||
href.push_str(&rendered_path);
|
href.push_str(&rendered_path);
|
||||||
|
|
||||||
out.write(&format!("<a href=\"{}\"><button role=\"menuitem\" class=\"language\" id=\"light\">", href))?;
|
out.write(&format!(
|
||||||
|
"<a href=\"{}\"><button role=\"menuitem\" class=\"language\" id=\"light\">",
|
||||||
|
href
|
||||||
|
))?;
|
||||||
out.write(&language.name)?;
|
out.write(&language.name)?;
|
||||||
out.write("</button></a>")?;
|
out.write("</button></a>")?;
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
|
pub mod language;
|
||||||
pub mod navigation;
|
pub mod navigation;
|
||||||
pub mod theme;
|
pub mod theme;
|
||||||
pub mod toc;
|
pub mod toc;
|
||||||
pub mod language;
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use crate::book::{BookItem, LoadedBook, Book};
|
use crate::book::{Book, BookItem, LoadedBook};
|
||||||
use crate::errors::*;
|
use crate::errors::*;
|
||||||
use crate::renderer::{RenderContext, Renderer};
|
use crate::renderer::{RenderContext, Renderer};
|
||||||
use crate::utils;
|
use crate::utils;
|
||||||
|
Loading…
Reference in New Issue
Block a user