translation links

rename with _
translation links
css for translation links
This commit is contained in:
Gambhiro 2017-01-10 06:35:56 +00:00
parent e7ba6fac85
commit c021940331
45 changed files with 270 additions and 177 deletions

View File

@ -12,7 +12,7 @@ build = "build.rs"
include = ["data"]
exclude = [
"book-example/*",
"data/html-template/_stylus",
"data/_html-template/_stylus",
]
@ -20,6 +20,7 @@ exclude = [
clap = "2.19.2"
handlebars = { version = "0.23.0", features = ["serde_type"] }
serde = "0.8"
serde_derive = "0.8"
serde_json = "0.8"
pulldown-cmark = "0.0.8"
regex = "0.1"

View File

@ -24,7 +24,7 @@ fn main() {
// Compile stylus stylesheet to css
let manifest_dir = env::var("CARGO_MANIFEST_DIR").unwrap();
let template_dir = Path::new(&manifest_dir).join("data/html-template/");
let template_dir = Path::new(&manifest_dir).join("data/_html-template/");
let stylus_dir = template_dir.join("_stylus/book.styl");
if !Command::new("stylus")

View File

@ -53,6 +53,10 @@
<i id="theme-toggle" class="fa fa-paint-brush"></i>
</div>
<div class="translations">
{{#translations}}{{/translations}}
</div>
<h1 class="menu-title">{{ title }}</h1>
<div class="right-buttons">
@ -60,6 +64,8 @@
</div>
</div>
<div id="content" class="content">
{{{ content }}}
</div>

View File

@ -17,18 +17,13 @@
.right-buttons { float: right }
}
.menu-title {
display: inline-block
h1.menu-title {
float: left
margin: 0 0 0 2em
padding: 0
font-weight: 200
font-size: 20px
line-height: 50px
position: absolute
top: 0
left: 0
right: 0
bottom: 0
text-align: center
margin: 0
opacity: 0
transition: opacity 0.5s ease-in-out

View File

@ -53,3 +53,21 @@
img { max-width: 100%; }
}
div.translations {
float: left
height: 50px
ul {
margin: 10px 0 0 0
display: inline-block
list-style-type: none
li {
display: inline-block
padding: 0.2em 0.5em
a {
text-decoration: none
&:hover { text-decoration: underline; }
}
}
}
}

View File

@ -37,7 +37,7 @@ table thead td {
font-weight: 700;
}
.sidebar {
position: fixed;
position: absolute;
left: 0;
top: 0;
bottom: 0;
@ -99,6 +99,7 @@ table thead td {
}
.page-wrapper {
position: absolute;
overflow-y: auto;
left: 315px;
right: 0;
top: 0;
@ -133,14 +134,7 @@ table thead td {
left: 0;
bottom: 0;
padding-right: 15px;
}
@media only screen and (max-width: 400px) {
.page {
/* Only prevent horizontal scrolling on screens with less than 100px for the content
A better way would be to somehow prevent horizontal scrolling all the time, but this causes scrolling problems on iOS Safari.
Also, would be better to only enable horizontal scrolling when it is needed (content does not fit on page) but I have no idea how to do that. */
overflow-x: hidden;
}
overflow-y: auto;
}
.content {
margin-left: auto;
@ -157,6 +151,25 @@ table thead td {
.content img {
max-width: 100%;
}
div.translations {
float: left;
height: 50px;
}
div.translations ul {
margin: 10px 0 0 0;
display: inline-block;
list-style-type: none;
}
div.translations ul li {
display: inline-block;
padding: 0.2em 0.5em;
}
div.translations ul li a {
text-decoration: none;
}
div.translations ul li a:hover {
text-decoration: underline;
}
.menu-bar {
position: relative;
height: 50px;
@ -181,18 +194,13 @@ table thead td {
.menu-bar .right-buttons {
float: right;
}
.menu-title {
display: inline-block;
h1.menu-title {
float: left;
margin: 0 0 0 2em;
padding: 0;
font-weight: 200;
font-size: 20px;
line-height: 50px;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
text-align: center;
margin: 0;
opacity: 0;
-ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=0)";
filter: alpha(opacity=0);
@ -211,7 +219,7 @@ table thead td {
font-size: 2.5em;
text-align: center;
text-decoration: none;
position: fixed;
position: absolute;
top: 50px /* Height of menu-bar */;
bottom: 0;
margin: 0;
@ -250,13 +258,7 @@ table thead td {
.nav-chapters:hover {
text-decoration: none;
}
.sidebar-hidden .previous {
left: 0;
}
.sidebar-visible .nav-chapters .previous {
left: 300px;
}
.sidebar-visible .mobile-nav-chapters .previous {
.previous {
left: 0;
}
.next {
@ -266,7 +268,6 @@ table thead td {
position: relative;
left: 10px;
z-index: 1000;
-webkit-border-radius: 4px;
border-radius: 4px;
font-size: 0.7em;
}
@ -303,7 +304,6 @@ table thead td {
position: relative;
display: inline-block;
margin-bottom: 50px;
-webkit-border-radius: 5px;
border-radius: 5px;
}
.next {
@ -406,7 +406,6 @@ table thead td {
display: inline-block;
vertical-align: middle;
padding: 0.1em 0.3em;
-webkit-border-radius: 3px;
border-radius: 3px;
}
.light pre {
@ -521,7 +520,6 @@ table thead td {
display: inline-block;
vertical-align: middle;
padding: 0.1em 0.3em;
-webkit-border-radius: 3px;
border-radius: 3px;
}
.coal pre {
@ -636,7 +634,6 @@ table thead td {
display: inline-block;
vertical-align: middle;
padding: 0.1em 0.3em;
-webkit-border-radius: 3px;
border-radius: 3px;
}
.navy pre {
@ -751,7 +748,6 @@ table thead td {
display: inline-block;
vertical-align: middle;
padding: 0.1em 0.3em;
-webkit-border-radius: 3px;
border-radius: 3px;
}
.rust pre {
@ -794,7 +790,6 @@ table thead td {
}
code {
background-color: #666;
-webkit-border-radius: 5px;
border-radius: 5px;
/* Force background to be printed in Chrome */
-webkit-print-color-adjust: exact;

View File

Before

Width:  |  Height:  |  Size: 348 KiB

After

Width:  |  Height:  |  Size: 348 KiB

View File

Before

Width:  |  Height:  |  Size: 5.5 KiB

After

Width:  |  Height:  |  Size: 5.5 KiB

View File

@ -1,116 +0,0 @@
<!DOCTYPE HTML>
<html lang="{{ language }}">
<head>
<meta charset="UTF-8">
<title>{{ title }}</title>
<meta content="text/html; charset=utf-8" http-equiv="Content-Type">
<meta name="description" content="{{ description }}">
<meta name="viewport" content="width=device-width, initial-scale=1">
<base href="{{ path_to_root }}">
<link rel="stylesheet" href="book.css">
<link href='https://fonts.googleapis.com/css?family=Open+Sans:300italic,400italic,600italic,700italic,800italic,400,300,600,700,800' rel='stylesheet' type='text/css'>
<link rel="shortcut icon" href="{{ favicon }}">
<!-- Font Awesome -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.3.0/css/font-awesome.min.css">
<link rel="stylesheet" href="highlight.css">
<link rel="stylesheet" href="tomorrow-night.css">
<!-- MathJax -->
<script type="text/javascript" src="https://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML"></script>
<!-- Fetch JQuery from CDN but have a local fallback -->
<script src="https://code.jquery.com/jquery-2.1.4.min.js"></script>
<script>
if (typeof jQuery == 'undefined') {
document.write(unescape("%3Cscript src='jquery.js'%3E%3C/script%3E"));
}
</script>
</head>
<body class="light">
<!-- Set the theme before any content is loaded, prevents flash -->
<script type="text/javascript">
var theme = localStorage.getItem('theme');
if (theme == null) { theme = 'light'; }
$('body').removeClass().addClass(theme);
</script>
<!-- Hide / unhide sidebar before it is displayed -->
<script type="text/javascript">
var sidebar = localStorage.getItem('sidebar');
if (sidebar === "hidden") { $("html").addClass("sidebar-hidden") }
else if (sidebar === "visible") { $("html").addClass("sidebar-visible") }
</script>
<div id="sidebar" class="sidebar">
{{#toc}}{{/toc}}
</div>
<div id="page-wrapper" class="page-wrapper">
<div class="page">
<div id="menu-bar" class="menu-bar">
<div class="left-buttons">
<i id="sidebar-toggle" class="fa fa-bars"></i>
<i id="theme-toggle" class="fa fa-paint-brush"></i>
</div>
<h1 class="menu-title">{{ title }}</h1>
<div class="right-buttons">
<i id="print-button" class="fa fa-print" title="Print this book"></i>
</div>
</div>
<div id="content" class="content">
{{{ content }}}
</div>
<!-- Mobile navigation buttons -->
{{#previous}}
<a href="{{link}}" class="mobile-nav-chapters previous">
<i class="fa fa-angle-left"></i>
</a>
{{/previous}}
{{#next}}
<a href="{{link}}" class="mobile-nav-chapters next">
<i class="fa fa-angle-right"></i>
</a>
{{/next}}
</div>
{{#previous}}
<a href="{{link}}" class="nav-chapters previous" title="You can navigate through the chapters using the arrow keys">
<i class="fa fa-angle-left"></i>
</a>
{{/previous}}
{{#next}}
<a href="{{link}}" class="nav-chapters next" title="You can navigate through the chapters using the arrow keys">
<i class="fa fa-angle-right"></i>
</a>
{{/next}}
</div>
<!-- Local fallback for Font Awesome -->
<script>
if ($(".fa").css("font-family") !== "FontAwesome") {
$('<link rel="stylesheet" type="text/css" href="_FontAwesome/css/font-awesome.css">').prependTo('head');
}
</script>
<!-- Livereload script (if served using the cli tool) -->
{{{livereload}}}
<script src="highlight.js"></script>
<script src="book.js"></script>
</body>
</html>

View File

@ -53,6 +53,9 @@ pub struct Chapter {
/// chapter's path to index.html.
pub dest_path: Option<PathBuf>,
/// Links to the corresponding translations.
pub translation_links: Option<Vec<TranslationLink>>,
/// The author of the chapter, or the book.
pub authors: Option<Vec<Author>>,
@ -73,6 +76,7 @@ impl Default for Chapter {
title: "Untitled".to_string(),
path: PathBuf::from("src".to_string()).join("untitled.md"),
dest_path: None,
translation_links: None,
authors: None,
translators: None,
description: None,
@ -153,6 +157,17 @@ impl Chapter {
self.css_class = Some(a.to_string());
}
if let Some(a) = data.get("translation_links") {
if let Some(b) = a.as_slice() {
let links: Vec<TranslationLink> = b.iter()
.filter_map(|x| x.as_table())
.map(|x| TranslationLink::from(x.to_owned()))
.collect::<Vec<TranslationLink>>();
self.translation_links = Some(links);
}
}
// Author name as a hash key.
if let Some(a) = data.get("author") {
if let Some(b) = a.as_str() {
@ -213,3 +228,40 @@ impl Chapter {
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TranslationLink {
pub code: String,
pub link: String,
}
impl Default for TranslationLink {
fn default() -> TranslationLink {
TranslationLink {
code: "".to_string(),
link: "".to_string(),
}
}
}
impl TranslationLink {
pub fn new(code: String, link: String) -> TranslationLink {
TranslationLink {
code: code,
link: link,
}
}
}
impl From<toml::Table> for TranslationLink {
fn from(data: toml::Table) -> TranslationLink {
let mut link = TranslationLink::default();
if let Some(x) = data.get("code") {
link.code = x.to_string().replace("\"", "");
}
if let Some(x) = data.get("link") {
link.link = x.to_string().replace("\"", "");
}
link
}
}

View File

@ -7,6 +7,9 @@ pub mod chapter;
pub use self::book::Book;
use renderer::{Renderer, HtmlHandlebars};
use self::chapter::TranslationLink;
use self::toc::{TocItem, TocContent};
use utils;
use std::env;
@ -32,7 +35,7 @@ pub struct MDBook {
/// A book doesn't necessarily has to have the template files. When not
/// found in the book's folder, the embedded static assets will be used.
///
/// Html Handlebars: `project_root` + `assets/html-template`.
/// Html Handlebars: `project_root` + `assets/_html-template`.
template_dir: PathBuf,
/// Output base for all books, relative to `project_root`. Defaults to
@ -367,6 +370,69 @@ impl MDBook {
self
}
pub fn link_translations(&mut self) -> &mut MDBook {
for (key, book) in self.translations.clone() {
let mut newbook: Book = book.clone();
newbook.toc = book.toc.iter()
.map(|item| {
match *item {
TocItem::Numbered(ref i) =>
TocItem::Numbered(self.set_translation_links(i)),
TocItem::Unnumbered(ref i) =>
TocItem::Unnumbered(self.set_translation_links(i)),
TocItem::Unlisted(ref i) =>
TocItem::Unlisted(self.set_translation_links(i)),
TocItem::Spacer =>
TocItem::Spacer,
}
}).collect::<Vec<TocItem>>();
self.translations.remove(&key);
self.translations.insert(key, newbook);
}
self
}
/// prepare a Vec of default links to point to the index.html of each translation
fn translation_index_links(&mut self) -> Vec<TranslationLink> {
let mut default_links: Vec<TranslationLink> = vec![];
let mut keys = self.translations.keys()
.map(|x| x.to_string())
.collect::<Vec<String>>();
keys.sort();
for key in keys {
let book = self.translations.get(&key).unwrap();
let z = self.get_dest_base();
let a = book.config.dest.strip_prefix(&z).unwrap();
let b = a.join("index.html");
let c = b.to_str().unwrap();
let link = TranslationLink::new(key, c.to_string());
default_links.push(link);
}
default_links
}
fn set_translation_links(&mut self, content: &TocContent) -> TocContent {
let default_links = self.translation_index_links();
let mut newcontent: TocContent = content.clone();
match newcontent.chapter.translation_links {
Some(_) => {},
None => {
newcontent.chapter.translation_links = Some(default_links);
}
}
newcontent
}
pub fn get_project_root(&self) -> &Path {
&self.project_root
}
@ -414,7 +480,7 @@ impl MDBook {
self.render_intent = intent;
match self.render_intent {
RenderIntent::HtmlHandlebars => {
self.set_template_dir(&PathBuf::from("assets").join("html-template"));
self.set_template_dir(&PathBuf::from("assets").join("_html-template"));
},
}
self

View File

@ -69,6 +69,11 @@
//!
//! Make sure to take a look at it.
#![feature(proc_macro)]
#[macro_use]
extern crate serde_derive;
extern crate includedir;
extern crate phf;

View File

@ -36,9 +36,14 @@ impl Renderer for HtmlHandlebars {
book_project.read_config();
book_project.parse_books();
book_project.link_translations();
// Clean output directory
try!(utils::fs::remove_dir_content(&book_project.get_dest_base()));
// FIXME don't remove dotfiles such as .git/ folder. It's a common
// practice to track gh-pages in a versioned output folder.
//try!(utils::fs::remove_dir_content(&book_project.get_dest_base()));
try!(self.render(&book_project));
@ -116,9 +121,9 @@ impl Renderer for HtmlHandlebars {
&book_project.get_dest_base());
} else {
try!(utils::fs::copy_data("data/html-template/**/*",
"data/html-template/",
vec!["data/html-template/_*"],
try!(utils::fs::copy_data("data/_html-template/**/*",
"data/_html-template/",
vec!["data/_html-template/_*"],
&book_project.get_dest_base()));
}
@ -133,7 +138,7 @@ impl Renderer for HtmlHandlebars {
let s = if tmpl_path.exists() {
try!(utils::fs::file_to_string(&tmpl_path))
} else {
try!(utils::fs::get_data_file("data/html-template/_layouts/page.hbs"))
try!(utils::fs::get_data_file("data/_html-template/_layouts/page.hbs"))
};
// Register template
@ -145,6 +150,7 @@ impl Renderer for HtmlHandlebars {
handlebars.register_helper("toc", Box::new(helpers::toc::RenderToc));
handlebars.register_helper("previous", Box::new(helpers::navigation::previous));
handlebars.register_helper("next", Box::new(helpers::navigation::next));
handlebars.register_helper("translations", Box::new(helpers::translations::TranslationsHelper));
// Check if book's dest directory exists
@ -332,6 +338,10 @@ fn make_data(book: &Book,
data.insert("path_to_root".to_owned(), utils::fs::path_to_root(&path).to_json());
if let Some(ref links) = chapter.translation_links {
data.insert("translation_links".to_owned(), links.to_json());
}
let chapters = try!(items_to_chapters(&book.toc, &book));
data.insert("chapters".to_owned(), chapters.to_json());

View File

@ -1,3 +1,4 @@
pub mod navigation;
pub mod toc;
pub mod playpen;
pub mod translations;

View File

@ -0,0 +1,34 @@
use std::path::Path;
use std::collections::{VecDeque, BTreeMap};
use serde_json;
use handlebars::{Handlebars, HelperDef, RenderError, RenderContext, Helper, Context};
#[derive(Clone, Copy)]
pub struct TranslationsHelper;
impl HelperDef for TranslationsHelper {
fn call(&self, c: &Context, _h: &Helper, _: &Handlebars, rc: &mut RenderContext) -> Result<(), RenderError> {
let translation_links = c.navigate(rc.get_path(), &VecDeque::new(), "translation_links");
let decoded: Vec<BTreeMap<String, String>> = serde_json::from_str(&translation_links.to_string()).unwrap();
if decoded.len() == 0 {
return Ok(());
}
try!(rc.writer.write("<ul class=\"translations\">".as_bytes()));
for item in decoded {
let empty = "".to_string();
let link = item.get("link").unwrap_or(&empty);
let code = item.get("code").unwrap_or(&empty);
let text = format!("<li><a href=\"{}\">{}</a></li>", link, code);
try!(rc.writer.write(text.as_bytes()));
}
try!(rc.writer.write("</ul>".as_bytes()));
Ok(())
}
}

View File

@ -1,3 +1,13 @@
+++
[[translation_links]]
code = "fr"
link = "fr/terrier.html"
[[translation_links]]
code = "hu"
link = "hu/nyuszi.html"
+++
# Down The Rabbit-Hole
![Rabbit](images/Rabbit.png)

View File

@ -9,7 +9,7 @@ use utils;
#[test]
fn it_copies_data_file() {
let dest_path = Path::new("the place was dark").join("and dusty and half-lost").join("book.css");
utils::fs::copy_data_file("data/html-template/css/books.css", &dest_path);
utils::fs::copy_data_file("data/_html-template/css/books.css", &dest_path);
let mut file = match File::open(&dest_path) {
Ok(f) => f,
@ -32,9 +32,9 @@ fn it_copies_data_file() {
fn it_copies_data_by_pattern() {
let dest_base = Path::new("in tangles of old alleys").join("near the quays");
if let Err(e) = utils::fs::copy_data("data/html-template/**/*",
"data/html-template/",
vec!["data/html-template/_*"],
if let Err(e) = utils::fs::copy_data("data/_html-template/**/*",
"data/_html-template/",
vec!["data/_html-template/_*"],
&dest_base) {
println!("Error: {:#?}", e);
return;

View File

@ -50,4 +50,20 @@ fn it_renders_multilanguage_book() {
assert!(proj.get_dest_base().join("images").join("Queen.jpg").exists());
// Test if translation links given in the TOML header were rendered
book_path = proj.translations.get("en").unwrap().config.get_dest();
chapter_path = book_path.join("rabbit-hole.html");
s = utils::fs::file_to_string(&chapter_path).unwrap();
assert!(s.contains("<a href=\"hu/nyuszi.html\">hu</a>"));
assert!(s.contains("<a href=\"fr/terrier.html\">fr</a>"));
// Test if default translation links are set
book_path = proj.translations.get("hu").unwrap().config.get_dest();
chapter_path = book_path.join("tarka-farka.html");
s = utils::fs::file_to_string(&chapter_path).unwrap();
assert!(s.contains("<a href=\"en/index.html\">en</a>"));
assert!(s.contains("<a href=\"hu/index.html\">hu</a>"));
assert!(s.contains("<a href=\"fr/index.html\">fr</a>"));
}

View File

@ -61,17 +61,17 @@ pub fn copy_data_file(src_path: &str, dest_path: &Path) -> Result<(), Box<Error>
/// `include_base` will be removed from the source path. This way the path
/// relative to the `dest_path` can be controlled.
///
/// The following will copy all files under "data/html-template/", excluding
/// folders that start with "_", take the "data/html-template/" part off the
/// The following will copy all files under "data/_html-template/", excluding
/// folders that start with "_", take the "data/_html-template/" part off the
/// source path, and write the entries to "assets" folder.
///
/// I.e. "data/html-template/css/book.css" will be written to
/// I.e. "data/_html-template/css/book.css" will be written to
/// "assets/css/book.css".
///
/// ```no_run
/// utils::fs::copy_data("data/html-template/**/*",
/// "data/html-template/",
/// vec!["data/html-template/_*"],
/// utils::fs::copy_data("data/_html-template/**/*",
/// "data/_html-template/",
/// vec!["data/_html-template/_*"],
/// &Path::new("assets"));
/// ```
pub fn copy_data(include_glob: &str,