From c1aeb9c596c937707139e4231f0ac29a51dbec44 Mon Sep 17 00:00:00 2001 From: Elisabeth Henry Date: Sun, 4 Jun 2017 04:31:39 +0200 Subject: [PATCH] Add basic support for embedded JS in interactive fiction --- src/lib/book.rs | 3 +- src/lib/bookoptions.rs | 5 +++ src/lib/html_if.rs | 70 ++++++++++++++++++++++--------------- src/lib/templates.rs | 4 +++ templates/html_if/script.js | 67 +++++++++++++++++++++++++++++++++++ 5 files changed, 120 insertions(+), 29 deletions(-) create mode 100644 templates/html_if/script.js diff --git a/src/lib/book.rs b/src/lib/book.rs index 01498e4..a48e6ce 100644 --- a/src/lib/book.rs +++ b/src/lib/book.rs @@ -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, diff --git a/src/lib/bookoptions.rs b/src/lib/bookoptions.rs index 5e72ad5..53d845f 100644 --- a/src/lib/bookoptions.rs +++ b/src/lib/bookoptions.rs @@ -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"), diff --git a/src/lib/html_if.rs b/src/lib/html_if.rs index 1623c29..0ff7a8c 100644 --- a/src/lib/html_if.rs +++ b/src/lib/html_if.rs @@ -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>+AsRef> + 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!("

\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 { - 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> { ", 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!("", 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() diff --git a/src/lib/templates.rs b/src/lib/templates.rs index 320d621..46f0581 100644 --- a/src/lib/templates.rs +++ b/src/lib/templates.rs @@ -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"); } diff --git a/templates/html_if/script.js b/templates/html_if/script.js new file mode 100644 index 0000000..4737a18 --- /dev/null +++ b/templates/html_if/script.js @@ -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); +};