1
0
mirror of https://github.com/lise-henry/crowbook synced 2024-09-22 13:21:37 +02:00

Add basic support for embedded JS in interactive fiction

This commit is contained in:
Elisabeth Henry 2017-06-04 04:31:39 +02:00
parent 40505c3c4e
commit c1aeb9c596
5 changed files with 120 additions and 29 deletions

View File

@ -26,7 +26,7 @@ use html_dir::{HtmlDir, ProofHtmlDir};
use html_if::{HtmlIf};
use latex::{Latex, ProofLatex, Pdf, ProofPdf};
use odt::{Odt};
use templates::{epub, html, epub3, latex, html_dir, highlight, html_single};
use templates::{epub, html, epub3, latex, html_dir, highlight, html_single, html_if};
use number::Number;
use resource_handler::ResourceHandler;
use logger::{Logger, InfoLevel};
@ -1023,6 +1023,7 @@ impl Book {
"html.dir.template" => html_dir::TEMPLATE,
"html.highlight.js" => highlight::JS,
"html.highlight.css" => highlight::CSS,
"html.if.js" => html_if::JS,
"tex.template" => latex::TEMPLATE,
_ => {
return Err(Error::config_parser(&self.source,

View File

@ -82,6 +82,9 @@ html.standalone.js:tpl # {single_js}
# {html_dir_opt}
html.dir.template:tpl # {html_dir_template}
# {html_if_opt}
html.if.js:tpl # {if_js}
# {epub_opt}
epub.version:int:2 # {epub_ver}
epub.highlight.theme:str # {epub_theme}
@ -193,6 +196,7 @@ crowbook.verbose:alias # {removed}
html_opt = lformat!("HTML options"),
html_single_opt = lformat!("Standalone HTML options"),
html_dir_opt = lformat!("Multifile HTML options"),
html_if_opt = lformat!("Interactive fiction HTML options"),
epub_opt = lformat!("EPUB options"),
tex_opt = lformat!("LaTeX options"),
rs_opt = lformat!("Resources option"),
@ -255,6 +259,7 @@ crowbook.verbose:alias # {removed}
one_chapter = lformat!("Display only one chapter at a time (with a button to display all)"),
single_html = lformat!("Path of an HTML template for standalone HTML"),
single_js = lformat!("Path of a javascript file"),
if_js = lformat!("Path of a javascript file"),
html_chapter_template = lformat!("Inline template for HTML chapter formatting"),
html_part_template = lformat!("Inline template for HTML part formatting"),

View File

@ -20,21 +20,25 @@ use html::HtmlRenderer;
use html::Highlight;
use book::{Book, compile_str};
use token::Token;
use templates::img;
use renderer::Renderer;
use book_renderer::BookRenderer;
use parser::Parser;
use text_view::view_as_text;
use rustc_serialize::base64::{self, ToBase64};
use std::convert::{AsMut, AsRef};
use std::io;
use std::mem;
/// Interactive fiction HTML renderer
///
/// Renders a standalone, self-contained HTML file
pub struct HtmlIfRenderer<'a> {
html: HtmlRenderer<'a>,
n_fn: u32,
curr_init: String,
fn_defs: String,
}
impl<'a> HtmlIfRenderer<'a> {
@ -46,7 +50,14 @@ impl<'a> HtmlIfRenderer<'a> {
.unwrap_or_else(|_| book.options.get_str("rendering.highlight.theme").unwrap()))?;
html.handler.set_images_mapping(true);
html.handler.set_base64(true);
Ok(HtmlIfRenderer { html: html })
Ok(
HtmlIfRenderer {
html: html,
n_fn: 0,
curr_init: String::new(),
fn_defs: String::new(),
}
)
}
/// Renders a token
@ -61,9 +72,24 @@ impl<'a> HtmlIfRenderer<'a> {
AsMut<HtmlRenderer<'a>>+AsRef<HtmlRenderer<'a>> + Renderer
{
match *token {
Token::CodeBlock(ref language, _) if language == "" => {
// Todo: allow for javascript code inclusion
HtmlRenderer::static_render_token(this, token)
Token::CodeBlock(ref language, ref v) if language == "" => {
let mut html_if: &mut HtmlIfRenderer = this.as_mut();
let code = view_as_text(v);
let id = html_if.n_fn;
html_if.fn_defs
.push_str(&format!("function fn_{id}() {{
{code}
}}\n",
id = id,
code = code));
html_if.curr_init
.push_str(&format!(" document.getElementById(\"result_{id}\").innerHTML = fn_{id}();\n",
id = id));
let content = format!("<p id = \"result_{}\"></p>\n",
(html_if.n_fn));
html_if.n_fn += 1;
Ok(content)
},
_ => HtmlRenderer::static_render_token(this, token)
}
@ -71,15 +97,6 @@ impl<'a> HtmlIfRenderer<'a> {
/// Render books as a standalone HTML file
pub fn render_book(&mut self) -> Result<String> {
let menu_svg = img::MENU_SVG.to_base64(base64::STANDARD);
let menu_svg = format!("data:image/svg+xml;base64,{}", menu_svg);
let book_svg = img::BOOK_SVG.to_base64(base64::STANDARD);
let book_svg = format!("data:image/svg+xml;base64,{}", book_svg);
let pages_svg = img::PAGES_SVG.to_base64(base64::STANDARD);
let pages_svg = format!("data:image/svg+xml;base64,{}", pages_svg);
let mut content = String::new();
let mut titles = vec![];
@ -90,7 +107,7 @@ impl<'a> HtmlIfRenderer<'a> {
self.html.handler.add_link(chapter.filename.as_ref(),
format!("#chapter-{}", i));
}
for (i, chapter) in self.html.book.chapters.iter().enumerate() {
let n = chapter.number;
let v = &chapter.content;
@ -127,7 +144,13 @@ impl<'a> HtmlIfRenderer<'a> {
</div>",
i,
HtmlRenderer::render_html(self, v, render_notes_chapter)?));
self.fn_defs.push_str(&format!("initFns.push(function () {{
{code}
}})\n",
code = self.curr_init));
self.curr_init = String::new();
}
self.html.source = Source::empty();
for chapter in &chapters {
@ -136,8 +159,6 @@ impl<'a> HtmlIfRenderer<'a> {
self.html.render_end_notes(&mut content);
let toc = self.html.toc.render(false);
// Render the CSS
let template_css = compile_str(self.html.book.get_template("html.css")?
.as_ref(),
@ -158,13 +179,12 @@ impl<'a> HtmlIfRenderer<'a> {
// Render the JS
let template_js =
compile_str(self.html.book.get_template("html.standalone.js")?.as_ref(),
compile_str(self.html.book.get_template("html.if.js")?.as_ref(),
&self.html.book.source,
"html.standalone.js")?;
let data = self.html.book.get_metadata(|s| Ok(s.to_owned()))?
.insert_str("book_svg", &book_svg)
.insert_str("pages_svg", &pages_svg)
.insert_bool("one_chapter", true)
.insert_str("js_prelude", mem::replace(&mut self.fn_defs, String::new()))
.insert_str("common_script",
self.html.book.get_template("html.js").unwrap().as_ref())
.build();
@ -183,19 +203,13 @@ impl<'a> HtmlIfRenderer<'a> {
.insert_str("style", css.as_ref())
.insert_str("print_style",
self.html.book.get_template("html.css.print").unwrap())
.insert_str("menu_svg", menu_svg)
.insert_str("book_svg", book_svg)
.insert_str("pages_svg", pages_svg)
.insert_str("footer", HtmlRenderer::get_footer(self)?)
.insert_str("header", HtmlRenderer::get_header(self)?);
.insert_str("header", HtmlRenderer::get_header(self)?)
.insert_bool("has_toc", false);
if let Ok(favicon) = self.html.book.options.get_path("html.icon") {
let favicon = self.html.handler.map_image(&self.html.book.source, favicon)?;
mapbuilder = mapbuilder.insert_str("favicon", format!("<link rel = \"icon\" href = \"{}\">", favicon));
}
if !self.html.toc.is_empty() {
mapbuilder = mapbuilder.insert_bool("has_toc", true);
mapbuilder = mapbuilder.insert_str("toc", toc)
}
if self.html.highlight == Highlight::Js {
let highlight_js = self.html.book.get_template("html.highlight.js")?
.as_bytes()

View File

@ -44,6 +44,10 @@ pub mod html_dir {
pub static TEMPLATE: &str = include_str!("../../templates/html_dir/template.html");
}
pub mod html_if {
pub static JS: &str = include_str!("../../templates/html_if/script.js");
}
pub mod latex {
pub static TEMPLATE: &str = include_str!("../../templates/latex/template.tex");
}

View File

@ -0,0 +1,67 @@
{{{common_script}}}
var initFns = [];
{{{js_prelude}}}
function showChapter(chap, noreset){
initFns[chap]();
if (!displayAll) {
var chapters = document.getElementsByClassName("chapter");
for (i = 0; i < chapters.length; i++) {
if (i == chap) {
chapters[i].style.display = "block";
} else {
chapters[i].style.display = "none";
}
}
var controls = document.getElementsByClassName("chapterControls");
for (i = 0; i < controls.length; i++){
if (i >= chap * 2-1 && i <= chap * 2) {
controls[i].style.display = "block";
} else {
controls[i].style.display = "none";
}
}
// Hide toc unless we're at first chapter
var toc = document.getElementById("toc");
if (toc && chap == 0) {
toc.style.display = "block";
}
if (toc && chap != 0) {
toc.style.display = "none";
}
if (!noreset) {
window.location.hash = "#chapter-"+chap;
}
} else {
window.location.hash = "#chapter-"+chap;
}
}
function getChapter(elem) {
if(!elem) {
return 0;
}
if(elem.className == "chapter") {
return parseInt(elem.id.substr("chapter-".length));
} else {
return getChapter(elem.parentElement);
}
}
window.onhashchange = function() {
var hash = document.location.hash;
if(!hash) {
showChapter(0, true);
} else {
var element = document.getElementById(hash.substr(1));
var chap = getChapter(element);
showChapter(chap, true);
}
};
window.onload = function(){
displayAll = false;
showChapter(0, true);
};