From 70e11ada26beff7e9d2ba87e2d4ee2b3f18e7c7a Mon Sep 17 00:00:00 2001 From: Jason Williams Date: Sun, 11 Aug 2019 21:07:40 +0100 Subject: [PATCH 01/12] first attempt --- book-example/src/format/config.md | 13 ++-- src/config.rs | 4 + src/renderer/html_handlebars/hbs_renderer.rs | 78 ++++++++++++++++++-- src/theme/book.js | 14 ++++ src/theme/mod.rs | 5 ++ src/theme/sw.js | 46 ++++++++++++ 6 files changed, 150 insertions(+), 10 deletions(-) create mode 100644 src/theme/sw.js diff --git a/book-example/src/format/config.md b/book-example/src/format/config.md index 434b6e75ce..62440246b3 100644 --- a/book-example/src/format/config.md +++ b/book-example/src/format/config.md @@ -1,8 +1,8 @@ # Configuration -You can configure the parameters for your book in the ***book.toml*** file. +You can configure the parameters for your book in the **_book.toml_** file. -Here is an example of what a ***book.toml*** file might look like: +Here is an example of what a **_book.toml_** file might look like: ```toml [book] @@ -45,6 +45,7 @@ This is general information about your book. - **language:** The main language of the book, which is used as a language attribute `` for example. **book.toml** + ```toml [book] title = "Example book" @@ -87,8 +88,8 @@ The following preprocessors are available and included by default: to say, all `README.md` would be rendered to an index file `index.html` in the rendered book. - **book.toml** + ```toml [build] build-dir = "build" @@ -148,6 +149,8 @@ The following configuration options are available: - **theme:** mdBook comes with a default theme and all the resource files needed for it. But if this option is set, mdBook will selectively overwrite the theme files with the ones found in the specified folder. +- **offline-support** Precache the chapters so that users can view the book + while offline. Available in [browsers supporting Service Worker](https://caniuse.com/#feat=serviceworkers). - **default-theme:** The theme color scheme to select by default in the 'Change Theme' dropdown. Defaults to `light`. - **preferred-dark-theme:** The default dark theme. This theme will be used if @@ -173,7 +176,7 @@ The following configuration options are available: - **playpen:** A subtable for configuring various playpen settings. - **search:** A subtable for configuring the in-browser search functionality. mdBook must be compiled with the `search` feature enabled (on by default). -- **git-repository-url:** A url to the git repository for the book. If provided +- **git-repository-url:** A url to the git repository for the book. If provided an icon link will be output in the menu bar of the book. - **git-repository-icon:** The FontAwesome icon class to use for the git repository link. Defaults to `fa-github`. @@ -186,7 +189,7 @@ Available configuration options for the `[output.html.playpen]` table: Defaults to `true`. - **line-numbers** Display line numbers on editable sections of code. Requires both `editable` and `copy-js` to be `true`. Defaults to `false`. -[Ace]: https://ace.c9.io/ +[ace]: https://ace.c9.io/ Available configuration options for the `[output.html.search]` table: diff --git a/src/config.rs b/src/config.rs index e93283c716..cfc7384747 100644 --- a/src/config.rs +++ b/src/config.rs @@ -447,6 +447,8 @@ pub struct HtmlConfig { pub curly_quotes: bool, /// Should mathjax be enabled? pub mathjax_support: bool, + /// Cache chapters for offline viewing + pub offline_support: bool, /// An optional google analytics code. pub google_analytics: Option, /// Additional CSS stylesheets to include in the rendered page's ``. @@ -609,6 +611,7 @@ mod tests { default-theme = "rust" curly-quotes = true google-analytics = "123456" + offline-support = true additional-css = ["./foo/bar/baz.css"] git-repository-url = "https://foo.com/" git-repository-icon = "fa-code-fork" @@ -647,6 +650,7 @@ mod tests { }; let html_should_be = HtmlConfig { curly_quotes: true, + offline_support: true, google_analytics: Some(String::from("123456")), additional_css: vec![PathBuf::from("./foo/bar/baz.css")], theme: Some(PathBuf::from("./themedir")), diff --git a/src/renderer/html_handlebars/hbs_renderer.rs b/src/renderer/html_handlebars/hbs_renderer.rs index 2ac0489166..ef437bd9b1 100644 --- a/src/renderer/html_handlebars/hbs_renderer.rs +++ b/src/renderer/html_handlebars/hbs_renderer.rs @@ -9,12 +9,21 @@ use crate::utils; use std::borrow::Cow; use std::collections::BTreeMap; use std::collections::HashMap; -use std::fs; +use std::collections::hash_map::DefaultHasher; +use std::hash::Hasher; +use std::fs::{self, OpenOptions}; +use std::io::Write; use std::path::{Path, PathBuf}; use handlebars::Handlebars; use regex::{Captures, Regex}; +#[derive(Default)] +pub struct ChapterFile { + pub path: String, + pub revision: u64, +} + #[derive(Default)] pub struct HtmlHandlebars; @@ -23,12 +32,46 @@ impl HtmlHandlebars { HtmlHandlebars } + fn build_service_worker(&self, build_dir: &Path, chapter_files: &Vec) -> Result<()> { + let path = build_dir.join("sw.js"); + let mut file = OpenOptions::new().append(true).open(path)?; + let mut content = String::from("\nconst chapters = [\n"); + + for chapter_file in chapter_files { + content.push_str(" { url: "); + + // Rewrite "/" to point to the current directory + // https://rust-lang-nursery.github.io/ => https://rust-lang-nursery.github.io/mdBook/ + // location.href is https://rust-lang-nursery.github.io/mdBook/sw.js + // so we remove the sw.js from the end to get the correct path + if chapter_file.path == "/" { + content.push_str("location.href.slice(0, location.href.length - 5)"); + } else { + content.push_str("'"); + content.push_str(&chapter_file.path); + content.push_str("'"); + } + + content.push_str(", revision: '"); + content.push_str(&chapter_file.revision.to_string()); + content.push_str("' },\n"); + } + + content.push_str("];\n"); + content.push_str("\nworkbox.precache(chapters);\n"); + + file.write(content.as_bytes())?; + + Ok(()) + } + fn render_item( &self, item: &BookItem, mut ctx: RenderItemContext<'_>, print_content: &mut String, - ) -> Result<()> { + ) -> Result> { + let mut chapter_files = Vec::new(); // FIXME: This should be made DRY-er and rely less on mutable state if let BookItem::Chapter(ref ch) = *item { let content = ch.content.clone(); @@ -47,6 +90,9 @@ impl HtmlHandlebars { .to_str() .chain_err(|| "Could not convert path to str")?; let filepath = Path::new(&ch.path).with_extension("html"); + let filepath_str = filepath.to_str().ok_or_else(|| { + Error::from(format!("Bad file name: {}", filepath.display())) + })?; // "print.html" is used for the print page. if ch.path == Path::new("print.md") { @@ -78,10 +124,14 @@ impl HtmlHandlebars { let rendered = ctx.handlebars.render("index", &ctx.data)?; let rendered = self.post_process(rendered, &ctx.html_config.playpen); + let rendered_bytes = rendered.into_bytes(); // Write to file debug!("Creating {}", filepath.display()); - utils::fs::write_file(&ctx.destination, &filepath, rendered.as_bytes())?; + utils::fs::write_file(&ctx.destination, &filepath, &rendered_bytes)?; + + let mut hasher = DefaultHasher::new(); + hasher.write(&rendered_bytes); if ctx.is_index { ctx.data.insert("path".to_owned(), json!("index.md")); @@ -91,10 +141,20 @@ impl HtmlHandlebars { let rendered_index = self.post_process(rendered_index, &ctx.html_config.playpen); debug!("Creating index.html from {}", path); utils::fs::write_file(&ctx.destination, "index.html", rendered_index.as_bytes())?; + + chapter_files.push(ChapterFile { + path: String::from("/"), + revision: hasher.finish(), + }); } + + chapter_files.push(ChapterFile { + path: filepath_str.into(), + revision: hasher.finish(), + }); } - Ok(()) + Ok(chapter_files) } #[cfg_attr(feature = "cargo-clippy", allow(clippy::let_and_return))] @@ -127,6 +187,7 @@ impl HtmlHandlebars { write_file(destination, "css/variables.css", &theme.variables_css)?; write_file(destination, "favicon.png", &theme.favicon)?; write_file(destination, "highlight.css", &theme.highlight_css)?; + write_file(destination, "sw.js", &theme.service_worker)?; write_file(destination, "tomorrow-night.css", &theme.tomorrow_night_css)?; write_file(destination, "ayu-highlight.css", &theme.ayu_highlight_css)?; write_file(destination, "highlight.js", &theme.highlight_js)?; @@ -326,6 +387,7 @@ impl Renderer for HtmlHandlebars { fs::create_dir_all(&destination) .chain_err(|| "Unexpected error when constructing destination path")?; + let mut chapter_files = Vec::new(); let mut is_index = true; for item in book.iter() { let ctx = RenderItemContext { @@ -335,7 +397,8 @@ impl Renderer for HtmlHandlebars { is_index, html_config: html_config.clone(), }; - self.render_item(item, ctx, &mut print_content)?; + let mut item_chapter_files = self.render_item(item, ctx, &mut print_content)?; + chapter_files.append(&mut item_chapter_files); is_index = false; } @@ -360,6 +423,11 @@ impl Renderer for HtmlHandlebars { self.copy_additional_css_and_js(&html_config, &ctx.root, &destination) .chain_err(|| "Unable to copy across additional CSS and JS")?; + if html_config.offline_support { + debug!("[*] Patching Service Worker to precache chapters"); + self.build_service_worker(destination, &chapter_files)?; + } + // Render search index #[cfg(feature = "search")] { diff --git a/src/theme/book.js b/src/theme/book.js index 2cfa4821af..077a6ae1e0 100644 --- a/src/theme/book.js +++ b/src/theme/book.js @@ -594,3 +594,17 @@ function playpen_text(playpen) { previousScrollTop = document.scrollingElement.scrollTop; }, { passive: true }); })(); + + +(function serviceWorker() { + var isLocalhost = + ["localhost", "127.0.0.1", ""].indexOf(document.location.hostname) !== -1; + + if ("serviceWorker" in navigator && true) { + navigator.serviceWorker + .register(document.baseURI + "sw.js") + .catch(function(error) { + console.error("Service worker registration failed:", error); + }); + } +})(); diff --git a/src/theme/mod.rs b/src/theme/mod.rs index aab96db732..3cc709ff96 100644 --- a/src/theme/mod.rs +++ b/src/theme/mod.rs @@ -23,6 +23,7 @@ pub static HIGHLIGHT_JS: &[u8] = include_bytes!("highlight.js"); pub static TOMORROW_NIGHT_CSS: &[u8] = include_bytes!("tomorrow-night.css"); pub static HIGHLIGHT_CSS: &[u8] = include_bytes!("highlight.css"); pub static AYU_HIGHLIGHT_CSS: &[u8] = include_bytes!("ayu-highlight.css"); +pub static SERVICE_WORKER: &'static [u8] = include_bytes!("sw.js"); pub static CLIPBOARD_JS: &[u8] = include_bytes!("clipboard.min.js"); pub static FONT_AWESOME: &[u8] = include_bytes!("FontAwesome/css/font-awesome.min.css"); pub static FONT_AWESOME_EOT: &[u8] = include_bytes!("FontAwesome/fonts/fontawesome-webfont.eot"); @@ -52,6 +53,7 @@ pub struct Theme { pub highlight_css: Vec, pub tomorrow_night_css: Vec, pub ayu_highlight_css: Vec, + pub service_worker: Vec, pub highlight_js: Vec, pub clipboard_js: Vec, } @@ -84,6 +86,7 @@ impl Theme { (theme_dir.join("favicon.png"), &mut theme.favicon), (theme_dir.join("highlight.js"), &mut theme.highlight_js), (theme_dir.join("clipboard.min.js"), &mut theme.clipboard_js), + (theme_dir.join("sw.js"), &mut theme.service_worker), (theme_dir.join("highlight.css"), &mut theme.highlight_css), ( theme_dir.join("tomorrow-night.css"), @@ -124,6 +127,7 @@ impl Default for Theme { highlight_css: HIGHLIGHT_CSS.to_owned(), tomorrow_night_css: TOMORROW_NIGHT_CSS.to_owned(), ayu_highlight_css: AYU_HIGHLIGHT_CSS.to_owned(), + service_worker: SERVICE_WORKER.to_owned(), highlight_js: HIGHLIGHT_JS.to_owned(), clipboard_js: CLIPBOARD_JS.to_owned(), } @@ -204,6 +208,7 @@ mod tests { highlight_css: Vec::new(), tomorrow_night_css: Vec::new(), ayu_highlight_css: Vec::new(), + service_worker: Vec::new(), highlight_js: Vec::new(), clipboard_js: Vec::new(), }; diff --git a/src/theme/sw.js b/src/theme/sw.js new file mode 100644 index 0000000000..f5ceb48a02 --- /dev/null +++ b/src/theme/sw.js @@ -0,0 +1,46 @@ +importScripts( + "https://unpkg.com/workbox-sw@2.0.3/build/importScripts/workbox-sw.dev.v2.0.3.js" +); + +// clientsClaims tells the Service Worker to take control as soon as it's activated +const workbox = new WorkboxSW({ clientsClaim: true }); + +// https://developers.google.com/web/fundamentals/instant-and-offline/offline-cookbook/#stale-while-revalidate +// TLDR: If there's a cached version available, use it, but fetch an update for next time. +const staleWhileRevalidate = workbox.strategies.staleWhileRevalidate(); + +// Remote fonts and JavaScript libraries +workbox.router.registerRoute( + new RegExp("https://fonts.googleapis.com/css"), + staleWhileRevalidate +); +workbox.router.registerRoute( + new RegExp("https://fonts.gstatic.com"), + staleWhileRevalidate +); +workbox.router.registerRoute( + new RegExp("https://maxcdn.bootstrapcdn.com/font-awesome"), + staleWhileRevalidate +); +workbox.router.registerRoute( + new RegExp("https://cdnjs.cloudflare.com/ajax/libs/mathjax"), + staleWhileRevalidate +); +workbox.router.registerRoute( + new RegExp("https://cdn.jsdelivr.net/clipboard.js"), + staleWhileRevalidate +); + +// Local resources +workbox.router.registerRoute(new RegExp(".js$"), staleWhileRevalidate); +workbox.router.registerRoute(new RegExp(".css$"), staleWhileRevalidate); + +// Here hbs_renderer.rs will inject the chapters, making sure they are precached. +// +// const chapters = [ +// { url: '/', revision: '11120' }, +// { url: 'cli/cli-tool.html', revision: '12722' }, +// { url: 'cli/init.html', revision: '12801' }, +// ]; +// +// workbox.precaching.precacheAndRoute(chapters); From 71d29e08bc3ef92fff706bd0d9c119d353a58fff Mon Sep 17 00:00:00 2001 From: Jason Williams Date: Mon, 12 Aug 2019 22:10:06 +0100 Subject: [PATCH 02/12] making progress --- src/renderer/html_handlebars/hbs_renderer.rs | 2 +- src/theme/book.js | 2 +- src/theme/sw.js | 21 +++++++++----------- 3 files changed, 11 insertions(+), 14 deletions(-) diff --git a/src/renderer/html_handlebars/hbs_renderer.rs b/src/renderer/html_handlebars/hbs_renderer.rs index ef437bd9b1..e6b852d38b 100644 --- a/src/renderer/html_handlebars/hbs_renderer.rs +++ b/src/renderer/html_handlebars/hbs_renderer.rs @@ -58,7 +58,7 @@ impl HtmlHandlebars { } content.push_str("];\n"); - content.push_str("\nworkbox.precache(chapters);\n"); + content.push_str("\nworkbox.precaching.precacheAndRoute(chapters);\n"); file.write(content.as_bytes())?; diff --git a/src/theme/book.js b/src/theme/book.js index 077a6ae1e0..58cf2f380d 100644 --- a/src/theme/book.js +++ b/src/theme/book.js @@ -600,7 +600,7 @@ function playpen_text(playpen) { var isLocalhost = ["localhost", "127.0.0.1", ""].indexOf(document.location.hostname) !== -1; - if ("serviceWorker" in navigator && true) { + if ("serviceWorker" in navigator && !isLocalhost) { navigator.serviceWorker .register(document.baseURI + "sw.js") .catch(function(error) { diff --git a/src/theme/sw.js b/src/theme/sw.js index f5ceb48a02..4ed2b1b8e8 100644 --- a/src/theme/sw.js +++ b/src/theme/sw.js @@ -1,39 +1,36 @@ importScripts( - "https://unpkg.com/workbox-sw@2.0.3/build/importScripts/workbox-sw.dev.v2.0.3.js" + "https://storage.googleapis.com/workbox-cdn/releases/4.3.1/workbox-sw.js" ); -// clientsClaims tells the Service Worker to take control as soon as it's activated -const workbox = new WorkboxSW({ clientsClaim: true }); - // https://developers.google.com/web/fundamentals/instant-and-offline/offline-cookbook/#stale-while-revalidate // TLDR: If there's a cached version available, use it, but fetch an update for next time. -const staleWhileRevalidate = workbox.strategies.staleWhileRevalidate(); +const staleWhileRevalidate = new workbox.strategies.StaleWhileRevalidate(); // Remote fonts and JavaScript libraries -workbox.router.registerRoute( +workbox.routing.registerRoute( new RegExp("https://fonts.googleapis.com/css"), staleWhileRevalidate ); -workbox.router.registerRoute( +workbox.routing.registerRoute( new RegExp("https://fonts.gstatic.com"), staleWhileRevalidate ); -workbox.router.registerRoute( +workbox.routing.registerRoute( new RegExp("https://maxcdn.bootstrapcdn.com/font-awesome"), staleWhileRevalidate ); -workbox.router.registerRoute( +workbox.routing.registerRoute( new RegExp("https://cdnjs.cloudflare.com/ajax/libs/mathjax"), staleWhileRevalidate ); -workbox.router.registerRoute( +workbox.routing.registerRoute( new RegExp("https://cdn.jsdelivr.net/clipboard.js"), staleWhileRevalidate ); // Local resources -workbox.router.registerRoute(new RegExp(".js$"), staleWhileRevalidate); -workbox.router.registerRoute(new RegExp(".css$"), staleWhileRevalidate); +workbox.routing.registerRoute(new RegExp(".js$"), staleWhileRevalidate); +workbox.routing.registerRoute(new RegExp(".css$"), staleWhileRevalidate); // Here hbs_renderer.rs will inject the chapters, making sure they are precached. // From 26f032fa11a80ce7b2b016a25ba93ca4e0194683 Mon Sep 17 00:00:00 2001 From: Jason Williams Date: Mon, 12 Aug 2019 23:58:01 +0100 Subject: [PATCH 03/12] changing to origin, adding .json and .woff files --- src/theme/book.js | 2 +- src/theme/sw.js | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/theme/book.js b/src/theme/book.js index 58cf2f380d..3fa25ad39f 100644 --- a/src/theme/book.js +++ b/src/theme/book.js @@ -602,7 +602,7 @@ function playpen_text(playpen) { if ("serviceWorker" in navigator && !isLocalhost) { navigator.serviceWorker - .register(document.baseURI + "sw.js") + .register(document.location.origin + "/sw.js") .catch(function(error) { console.error("Service worker registration failed:", error); }); diff --git a/src/theme/sw.js b/src/theme/sw.js index 4ed2b1b8e8..a1b5ecf447 100644 --- a/src/theme/sw.js +++ b/src/theme/sw.js @@ -29,8 +29,10 @@ workbox.routing.registerRoute( ); // Local resources -workbox.routing.registerRoute(new RegExp(".js$"), staleWhileRevalidate); -workbox.routing.registerRoute(new RegExp(".css$"), staleWhileRevalidate); +workbox.routing.registerRoute( + new RegExp(".woff2?|.ttf|.css|.js|.json"), + staleWhileRevalidate +); // Here hbs_renderer.rs will inject the chapters, making sure they are precached. // From 8a92510f38647533f724ee6c5428dced3427f075 Mon Sep 17 00:00:00 2001 From: Jason Williams Date: Tue, 13 Aug 2019 00:31:38 +0100 Subject: [PATCH 04/12] added .png and .svg plus fixed test --- src/theme/mod.rs | 1 + src/theme/sw.js | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/theme/mod.rs b/src/theme/mod.rs index 3cc709ff96..23fcdde941 100644 --- a/src/theme/mod.rs +++ b/src/theme/mod.rs @@ -180,6 +180,7 @@ mod tests { "css/variables.css", "book.js", "highlight.js", + "sw.js", "tomorrow-night.css", "highlight.css", "ayu-highlight.css", diff --git a/src/theme/sw.js b/src/theme/sw.js index a1b5ecf447..422a24d9d4 100644 --- a/src/theme/sw.js +++ b/src/theme/sw.js @@ -30,7 +30,7 @@ workbox.routing.registerRoute( // Local resources workbox.routing.registerRoute( - new RegExp(".woff2?|.ttf|.css|.js|.json"), + new RegExp(".woff2?|.ttf|.css|.js|.json|.png|.svg"), staleWhileRevalidate ); From fc54175b80795e3307ca006007e95a2e6c5c2524 Mon Sep 17 00:00:00 2001 From: Jason Williams Date: Thu, 15 Aug 2019 20:02:42 +0100 Subject: [PATCH 05/12] sw.js should be on the relative path, same as other js/css assets --- src/theme/book.js | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/theme/book.js b/src/theme/book.js index 3fa25ad39f..442f335ab1 100644 --- a/src/theme/book.js +++ b/src/theme/book.js @@ -601,10 +601,8 @@ function playpen_text(playpen) { ["localhost", "127.0.0.1", ""].indexOf(document.location.hostname) !== -1; if ("serviceWorker" in navigator && !isLocalhost) { - navigator.serviceWorker - .register(document.location.origin + "/sw.js") - .catch(function(error) { - console.error("Service worker registration failed:", error); - }); + navigator.serviceWorker.register("sw.js").catch(function(error) { + console.error("Service worker registration failed:", error); + }); } })(); From 655961db5949889da34d81fcf4a14050418819b5 Mon Sep 17 00:00:00 2001 From: Jason Williams Date: Tue, 13 Aug 2019 15:11:14 +0100 Subject: [PATCH 06/12] adding cargo fmt --- src/renderer/html_handlebars/hbs_renderer.rs | 28 +++++++++++--------- 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/src/renderer/html_handlebars/hbs_renderer.rs b/src/renderer/html_handlebars/hbs_renderer.rs index e6b852d38b..8bd00b5194 100644 --- a/src/renderer/html_handlebars/hbs_renderer.rs +++ b/src/renderer/html_handlebars/hbs_renderer.rs @@ -7,11 +7,11 @@ use crate::theme::{self, playpen_editor, Theme}; use crate::utils; use std::borrow::Cow; +use std::collections::hash_map::DefaultHasher; use std::collections::BTreeMap; use std::collections::HashMap; -use std::collections::hash_map::DefaultHasher; -use std::hash::Hasher; use std::fs::{self, OpenOptions}; +use std::hash::Hasher; use std::io::Write; use std::path::{Path, PathBuf}; @@ -32,15 +32,19 @@ impl HtmlHandlebars { HtmlHandlebars } - fn build_service_worker(&self, build_dir: &Path, chapter_files: &Vec) -> Result<()> { + fn build_service_worker( + &self, + build_dir: &Path, + chapter_files: &Vec, + ) -> Result<()> { let path = build_dir.join("sw.js"); let mut file = OpenOptions::new().append(true).open(path)?; let mut content = String::from("\nconst chapters = [\n"); - for chapter_file in chapter_files { + for chapter_file in chapter_files { content.push_str(" { url: "); - // Rewrite "/" to point to the current directory + // Rewrite "/" to point to the current directory // https://rust-lang-nursery.github.io/ => https://rust-lang-nursery.github.io/mdBook/ // location.href is https://rust-lang-nursery.github.io/mdBook/sw.js // so we remove the sw.js from the end to get the correct path @@ -52,17 +56,17 @@ impl HtmlHandlebars { content.push_str("'"); } - content.push_str(", revision: '"); + content.push_str(", revision: '"); content.push_str(&chapter_file.revision.to_string()); content.push_str("' },\n"); } - content.push_str("];\n"); + content.push_str("];\n"); content.push_str("\nworkbox.precaching.precacheAndRoute(chapters);\n"); - file.write(content.as_bytes())?; + file.write(content.as_bytes())?; - Ok(()) + Ok(()) } fn render_item( @@ -90,9 +94,9 @@ impl HtmlHandlebars { .to_str() .chain_err(|| "Could not convert path to str")?; let filepath = Path::new(&ch.path).with_extension("html"); - let filepath_str = filepath.to_str().ok_or_else(|| { - Error::from(format!("Bad file name: {}", filepath.display())) - })?; + let filepath_str = filepath + .to_str() + .ok_or_else(|| Error::from(format!("Bad file name: {}", filepath.display())))?; // "print.html" is used for the print page. if ch.path == Path::new("print.md") { From 6c687bb21212be2668cc44670c48dc0ac619b328 Mon Sep 17 00:00:00 2001 From: Jason Williams Date: Fri, 18 Oct 2019 18:34:41 +0100 Subject: [PATCH 07/12] fixing regex --- src/theme/sw.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/theme/sw.js b/src/theme/sw.js index 422a24d9d4..562d11ace4 100644 --- a/src/theme/sw.js +++ b/src/theme/sw.js @@ -30,7 +30,7 @@ workbox.routing.registerRoute( // Local resources workbox.routing.registerRoute( - new RegExp(".woff2?|.ttf|.css|.js|.json|.png|.svg"), + /\.(woff2?|ttf|css|js|json|png|svg)$/, staleWhileRevalidate ); From 8827fecf303fe8fe50a3a6d9bc4f9df3d281aa08 Mon Sep 17 00:00:00 2001 From: Weihang Lo Date: Fri, 25 Oct 2019 18:53:03 +0800 Subject: [PATCH 08/12] Fix relative path of service worker script --- book-example/book.toml | 1 + src/renderer/html_handlebars/hbs_renderer.rs | 4 ++++ src/theme/book.js | 14 ++++++++++---- src/theme/index.hbs | 9 ++++++++- 4 files changed, 23 insertions(+), 5 deletions(-) diff --git a/book-example/book.toml b/book-example/book.toml index 100247fac2..3f3802c2ec 100644 --- a/book-example/book.toml +++ b/book-example/book.toml @@ -6,6 +6,7 @@ language = "en" [output.html] mathjax-support = true +offline-support = true [output.html.playpen] editable = true diff --git a/src/renderer/html_handlebars/hbs_renderer.rs b/src/renderer/html_handlebars/hbs_renderer.rs index 8bd00b5194..7351c6ee64 100644 --- a/src/renderer/html_handlebars/hbs_renderer.rs +++ b/src/renderer/html_handlebars/hbs_renderer.rs @@ -548,6 +548,10 @@ fn make_data( ) } + if html_config.offline_support { + data.insert("offline_support".to_owned(), json!(true)); + } + if let Some(ref git_repository_url) = html_config.git_repository_url { data.insert("git_repository_url".to_owned(), json!(git_repository_url)); } diff --git a/src/theme/book.js b/src/theme/book.js index 442f335ab1..4d25ed088f 100644 --- a/src/theme/book.js +++ b/src/theme/book.js @@ -600,9 +600,15 @@ function playpen_text(playpen) { var isLocalhost = ["localhost", "127.0.0.1", ""].indexOf(document.location.hostname) !== -1; - if ("serviceWorker" in navigator && !isLocalhost) { - navigator.serviceWorker.register("sw.js").catch(function(error) { - console.error("Service worker registration failed:", error); - }); + if ( + window.sevice_worker_script_src && + "serviceWorker" in navigator && + !isLocalhost + ) { + navigator.serviceWorker + .register(window.sevice_worker_script_src) + .catch(function(error) { + console.error("Service worker registration failed:", error); + }); } })(); diff --git a/src/theme/index.hbs b/src/theme/index.hbs index 99ae32ab96..7d3fb95d33 100644 --- a/src/theme/index.hbs +++ b/src/theme/index.hbs @@ -235,7 +235,7 @@ window.playpen_line_numbers = true; {{/if}} - + {{#if playpen_copyable}} {{/if}} + {{#if offline_support}} + + {{/if}} + From 85051be39a33099b4e475742b50519dd0b7251fb Mon Sep 17 00:00:00 2001 From: Jason Williams Date: Wed, 18 Dec 2019 20:10:54 +0000 Subject: [PATCH 09/12] rename sevice to service --- src/theme/book.js | 4 ++-- src/theme/index.hbs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/theme/book.js b/src/theme/book.js index 4d25ed088f..73f6cf8739 100644 --- a/src/theme/book.js +++ b/src/theme/book.js @@ -601,12 +601,12 @@ function playpen_text(playpen) { ["localhost", "127.0.0.1", ""].indexOf(document.location.hostname) !== -1; if ( - window.sevice_worker_script_src && + window.service_worker_script_src && "serviceWorker" in navigator && !isLocalhost ) { navigator.serviceWorker - .register(window.sevice_worker_script_src) + .register(window.service_worker_script_src) .catch(function(error) { console.error("Service worker registration failed:", error); }); diff --git a/src/theme/index.hbs b/src/theme/index.hbs index 7d3fb95d33..9e250647b6 100644 --- a/src/theme/index.hbs +++ b/src/theme/index.hbs @@ -259,7 +259,7 @@ {{#if offline_support}} {{/if}} From 14647f39ea93079cc33e07eaa60d926eafc332b4 Mon Sep 17 00:00:00 2001 From: Jason Williams Date: Fri, 20 Dec 2019 16:13:32 +0000 Subject: [PATCH 10/12] * Search now works offline (query strings are ignored and matched against the cache) * Extra .woff2 resources now cache properly, version querystring was causing the cache to fail. --- src/renderer/html_handlebars/hbs_renderer.rs | 2 +- src/theme/sw.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/renderer/html_handlebars/hbs_renderer.rs b/src/renderer/html_handlebars/hbs_renderer.rs index 7351c6ee64..2eb2344fe2 100644 --- a/src/renderer/html_handlebars/hbs_renderer.rs +++ b/src/renderer/html_handlebars/hbs_renderer.rs @@ -62,7 +62,7 @@ impl HtmlHandlebars { } content.push_str("];\n"); - content.push_str("\nworkbox.precaching.precacheAndRoute(chapters);\n"); + content.push_str("\nworkbox.precaching.precacheAndRoute(chapters, {ignoreURLParametersMatching: [/.*/]});\n"); file.write(content.as_bytes())?; diff --git a/src/theme/sw.js b/src/theme/sw.js index 562d11ace4..c9d90c81b9 100644 --- a/src/theme/sw.js +++ b/src/theme/sw.js @@ -30,7 +30,7 @@ workbox.routing.registerRoute( // Local resources workbox.routing.registerRoute( - /\.(woff2?|ttf|css|js|json|png|svg)$/, + /\.(woff2?|ttf|css|js|json|png|svg)(\?v\=.*)?$/, staleWhileRevalidate ); From 0915483565d943119814586a11a8957d613a8ec1 Mon Sep 17 00:00:00 2001 From: Jason Williams Date: Mon, 30 Dec 2019 15:34:50 +0000 Subject: [PATCH 11/12] rust fmt --- src/utils/mod.rs | 6 ++---- tests/rendered_output.rs | 15 +++++++++------ 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 9cf7c4dc1f..0a7ebfa80c 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -359,8 +359,7 @@ more text with spaces ``` "#; - let expected = - r#"
+ let expected = r#"
"#; assert_eq!(render_markdown(input, false), expected); assert_eq!(render_markdown(input, true), expected); @@ -373,8 +372,7 @@ more text with spaces ``` "#; - let expected = - r#"
+ let expected = r#"
"#; assert_eq!(render_markdown(input, false), expected); assert_eq!(render_markdown(input, true), expected); diff --git a/tests/rendered_output.rs b/tests/rendered_output.rs index a85f1aaf7f..800fc33539 100644 --- a/tests/rendered_output.rs +++ b/tests/rendered_output.rs @@ -481,12 +481,15 @@ fn markdown_options() { "bim", ], ); - assert_contains_strings(&path, &[ - r##"1"##, - r##"2"##, - r##"
1"##, - r##"
2"##, - ]); + assert_contains_strings( + &path, + &[ + r##"1"##, + r##"2"##, + r##"
1"##, + r##"
2"##, + ], + ); assert_contains_strings(&path, &["strikethrough example"]); assert_contains_strings( &path, From ca2c0e167f7a1a21143d07a9095f67d7bbd1a02a Mon Sep 17 00:00:00 2001 From: Jason Williams Date: Tue, 25 Feb 2020 22:44:22 +0000 Subject: [PATCH 12/12] Adding skipWaiting to see if cache is busted --- src/theme/sw.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/theme/sw.js b/src/theme/sw.js index c9d90c81b9..cc3f960226 100644 --- a/src/theme/sw.js +++ b/src/theme/sw.js @@ -2,6 +2,11 @@ importScripts( "https://storage.googleapis.com/workbox-cdn/releases/4.3.1/workbox-sw.js" ); +self.addEventListener("install", event => { + // Take over old service worker immediately, should hopefully fix weird caching issues + self.skipWaiting(); +}); + // https://developers.google.com/web/fundamentals/instant-and-offline/offline-cookbook/#stale-while-revalidate // TLDR: If there's a cached version available, use it, but fetch an update for next time. const staleWhileRevalidate = new workbox.strategies.StaleWhileRevalidate();