From fde88c22a8367dee9a70b7240b3461cb611079dc Mon Sep 17 00:00:00 2001 From: Jade Date: Fri, 30 Jul 2021 00:13:25 -0700 Subject: [PATCH 01/21] Fix an x overflow with long inline code Spotted on https://rust-lang.github.io/rfcs/2603-rust-symbol-name-mangling-v0.html --- src/theme/css/general.css | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/theme/css/general.css b/src/theme/css/general.css index ef2ba504..5c9d4fa0 100644 --- a/src/theme/css/general.css +++ b/src/theme/css/general.css @@ -26,6 +26,11 @@ code { font-size: 0.875em; /* please adjust the ace font size accordingly in editor.js */ } +/* make long inline code not x overflow */ +:not(pre) > code { + word-break: break-word; +} + /* Don't change font size in headers. */ h1 code, h2 code, h3 code, h4 code, h5 code, h6 code { font-size: unset; From 85df785cd36d8bf986cf21e41ee072de995f446a Mon Sep 17 00:00:00 2001 From: Jade Date: Fri, 30 Jul 2021 01:11:36 -0700 Subject: [PATCH 02/21] Wrap tables in an overflow-x wrapper div --- src/theme/css/general.css | 5 +++++ src/utils/mod.rs | 18 +++++++++++++++++- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/src/theme/css/general.css b/src/theme/css/general.css index 5c9d4fa0..ba899143 100644 --- a/src/theme/css/general.css +++ b/src/theme/css/general.css @@ -31,6 +31,11 @@ code { word-break: break-word; } +/* make wide tables scroll if they overflow */ +.table-wrapper { + overflow-x: auto; +} + /* Don't change font size in headers. */ h1 code, h2 code, h3 code, h4 code, h5 code, h6 code { font-size: unset; diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 44494a8b..174fcceb 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -177,12 +177,28 @@ pub fn render_markdown_with_path(text: &str, curly_quotes: bool, path: Option<&P let p = new_cmark_parser(text, curly_quotes); let events = p .map(clean_codeblock_headers) - .map(|event| adjust_links(event, path)); + .map(|event| adjust_links(event, path)) + .flat_map(|event| { + let (a, b) = wrap_tables(event); + a.into_iter().chain(b) + }); html::push_html(&mut s, events); s } +/// Wraps tables in a `.table-wrapper` class to apply overflow-x rules to. +fn wrap_tables(event: Event<'_>) -> (Option>, Option>) { + match event { + Event::Start(Tag::Table(_)) => ( + Some(Event::Html(r#"
"#.into())), + Some(event), + ), + Event::End(Tag::Table(_)) => (Some(event), Some(Event::Html(r#"
"#.into()))), + _ => (Some(event), None), + } +} + fn clean_codeblock_headers(event: Event<'_>) -> Event<'_> { match event { Event::Start(Tag::CodeBlock(CodeBlockKind::Fenced(ref info))) => { From 89b580ab5278424f22cd7ac2be11c3d15beb8f79 Mon Sep 17 00:00:00 2001 From: Jade Date: Fri, 30 Jul 2021 01:17:02 -0700 Subject: [PATCH 03/21] Add a test for the table div-wrapping --- src/utils/mod.rs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 174fcceb..cd0a4837 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -257,6 +257,22 @@ mod tests { ); } + #[test] + fn it_can_wrap_tables() { + let src = r#" +| Original | Punycode | Punycode + Encoding | +|-----------------|-----------------|---------------------| +| føø | f-5gaa | f_5gaa | +"#; + let out = r#" +
+ +
OriginalPunycodePunycode + Encoding
føøf-5gaaf_5gaa
+
+"#.trim(); + assert_eq!(render_markdown(src, false), out); + } + #[test] fn it_can_keep_quotes_straight() { assert_eq!(render_markdown("'one'", false), "

'one'

\n"); From 59569984e2a4887504a6c579ac02e5b0c47b3e4b Mon Sep 17 00:00:00 2001 From: Jade Date: Wed, 8 Sep 2021 00:43:52 -0700 Subject: [PATCH 04/21] Address review: use overflow-wrap everywhere --- src/theme/css/general.css | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/theme/css/general.css b/src/theme/css/general.css index ba899143..5096bad1 100644 --- a/src/theme/css/general.css +++ b/src/theme/css/general.css @@ -26,9 +26,9 @@ code { font-size: 0.875em; /* please adjust the ace font size accordingly in editor.js */ } -/* make long inline code not x overflow */ -:not(pre) > code { - word-break: break-word; +/* make long words/inline code not x overflow */ +main { + overflow-wrap: anywhere; } /* make wide tables scroll if they overflow */ From 4ae7ab5e872ebb8fd1165a9beb0745148a056277 Mon Sep 17 00:00:00 2001 From: Jade Lovelace Date: Thu, 27 Jan 2022 18:42:39 -0800 Subject: [PATCH 05/21] switch to break-word as suggested --- src/theme/css/general.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/theme/css/general.css b/src/theme/css/general.css index 5096bad1..3174dca0 100644 --- a/src/theme/css/general.css +++ b/src/theme/css/general.css @@ -28,7 +28,7 @@ code { /* make long words/inline code not x overflow */ main { - overflow-wrap: anywhere; + overflow-wrap: break-word; } /* make wide tables scroll if they overflow */ From 37d756ae75ef79d4b292ec08c06ad88cb90a6592 Mon Sep 17 00:00:00 2001 From: Eric Huss Date: Mon, 16 May 2022 18:35:24 -0700 Subject: [PATCH 06/21] Adjust overlap of code buttons with code blocks. --- src/renderer/html_handlebars/hbs_renderer.rs | 15 +++++------ src/theme/ayu-highlight.css | 1 - src/theme/css/chrome.css | 28 ++++++++++++++++++-- src/theme/css/general.css | 3 +-- src/theme/highlight.css | 1 - src/theme/tomorrow-night.css | 2 -- 6 files changed, 33 insertions(+), 17 deletions(-) diff --git a/src/renderer/html_handlebars/hbs_renderer.rs b/src/renderer/html_handlebars/hbs_renderer.rs index 8a13db9f..d32d45e4 100644 --- a/src/renderer/html_handlebars/hbs_renderer.rs +++ b/src/renderer/html_handlebars/hbs_renderer.rs @@ -880,11 +880,8 @@ fn add_playground_pre( // we need to inject our own main let (attrs, code) = partition_source(code); - format!( - "\n# #![allow(unused)]\n{}#fn main() {{\n{}#}}", - attrs, code - ) - .into() + format!("# #![allow(unused)]\n{}#fn main() {{\n{}#}}", attrs, code) + .into() }; hide_lines(&content) } @@ -1007,7 +1004,7 @@ mod tests { fn add_playground() { let inputs = [ ("x()", - "
\n#![allow(unused)]\nfn main() {\nx()\n}\n
"), + "
#![allow(unused)]\nfn main() {\nx()\n}\n
"), ("fn main() {}", "
fn main() {}\n
"), ("let s = \"foo\n # bar\n\";", @@ -1037,7 +1034,7 @@ mod tests { fn add_playground_edition2015() { let inputs = [ ("x()", - "
\n#![allow(unused)]\nfn main() {\nx()\n}\n
"), + "
#![allow(unused)]\nfn main() {\nx()\n}\n
"), ("fn main() {}", "
fn main() {}\n
"), ("fn main() {}", @@ -1061,7 +1058,7 @@ mod tests { fn add_playground_edition2018() { let inputs = [ ("x()", - "
\n#![allow(unused)]\nfn main() {\nx()\n}\n
"), + "
#![allow(unused)]\nfn main() {\nx()\n}\n
"), ("fn main() {}", "
fn main() {}\n
"), ("fn main() {}", @@ -1085,7 +1082,7 @@ mod tests { fn add_playground_edition2021() { let inputs = [ ("x()", - "
\n#![allow(unused)]\nfn main() {\nx()\n}\n
"), + "
#![allow(unused)]\nfn main() {\nx()\n}\n
"), ("fn main() {}", "
fn main() {}\n
"), ("fn main() {}", diff --git a/src/theme/ayu-highlight.css b/src/theme/ayu-highlight.css index 0c45c6f1..32c94322 100644 --- a/src/theme/ayu-highlight.css +++ b/src/theme/ayu-highlight.css @@ -8,7 +8,6 @@ Original by Dempfi (https://github.com/dempfi/ayu) overflow-x: auto; background: #191f26; color: #e6e1cf; - padding: 0.5em; } .hljs-comment, diff --git a/src/theme/css/chrome.css b/src/theme/css/chrome.css index 21c08b93..32b758a1 100644 --- a/src/theme/css/chrome.css +++ b/src/theme/css/chrome.css @@ -208,8 +208,10 @@ pre { pre > .buttons { position: absolute; z-index: 100; - right: 5px; - top: 5px; + right: 0px; + top: 2px; + margin: 0px; + padding: 2px 0px; color: var(--sidebar-fg); cursor: pointer; @@ -225,7 +227,29 @@ pre > .buttons button { background: transparent; border: none; cursor: inherit; + margin: 0px; + padding: 0px 0.5rem; + font-size: 14px; } +@media (pointer: coarse) { + pre > .buttons button { + /* On mobile, make it easier to tap buttons. */ + padding: 0 1rem; + } +} +code { + padding: 1.6rem 1rem; +} + +/* FIXME: ACE editors overlap their buttons because ACE does absolute + positioning within the code block which breaks padding. The only solution I + can think of is to move the padding to the outer pre tag (or insert a div + wrapper), but that would require fixing a whole bunch of CSS rules. +*/ +.hljs.ace_editor { + padding: 0rem 0rem; +} + pre > .result { margin-top: 10px; } diff --git a/src/theme/css/general.css b/src/theme/css/general.css index ef2ba504..fc7ccc2f 100644 --- a/src/theme/css/general.css +++ b/src/theme/css/general.css @@ -80,8 +80,7 @@ h6:target::before { .content { overflow-y: auto; - padding: 0 15px; - padding-bottom: 50px; + padding: 0 5px 50px 5px; } .content main { margin-left: auto; diff --git a/src/theme/highlight.css b/src/theme/highlight.css index c2343227..ba57b82b 100644 --- a/src/theme/highlight.css +++ b/src/theme/highlight.css @@ -61,7 +61,6 @@ overflow-x: auto; background: #f6f7f6; color: #000; - padding: 0.5em; } .hljs-emphasis { diff --git a/src/theme/tomorrow-night.css b/src/theme/tomorrow-night.css index f7197925..5b4aca77 100644 --- a/src/theme/tomorrow-night.css +++ b/src/theme/tomorrow-night.css @@ -81,8 +81,6 @@ overflow-x: auto; background: #1d1f21; color: #c5c8c6; - padding: 0.5em; - -webkit-text-size-adjust: none; } .coffeescript .javascript, From d65ce55453b196f0a86a9636a98222a3d68e4572 Mon Sep 17 00:00:00 2001 From: Matthew Woodcraft Date: Sun, 22 May 2022 13:37:19 +0100 Subject: [PATCH 07/21] When creating the search index, omit words longer than 80 characters This avoids creating deeply nested objects in searchindex.json --- src/renderer/html_handlebars/search.rs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/renderer/html_handlebars/search.rs b/src/renderer/html_handlebars/search.rs index 0a59ffe9..b39569d4 100644 --- a/src/renderer/html_handlebars/search.rs +++ b/src/renderer/html_handlebars/search.rs @@ -13,6 +13,8 @@ use crate::utils; use serde::Serialize; +const MAX_WORD_LENGTH_TO_INDEX: usize = 80; + /// Creates all files required for search. pub fn create_files(search_config: &Search, destination: &Path, book: &Book) -> Result<()> { let mut index = Index::new(&["title", "body", "breadcrumbs"]); @@ -44,6 +46,15 @@ pub fn create_files(search_config: &Search, destination: &Path, book: &Book) -> Ok(()) } +/// Tokenizes in the same way as elasticlunr-rs (for English), but also drops long tokens. +fn tokenize(text: &str) -> Vec { + text.split(|c: char| c.is_whitespace() || c == '-') + .filter(|s| !s.is_empty()) + .map(|s| s.trim().to_lowercase()) + .filter(|s| s.len() <= MAX_WORD_LENGTH_TO_INDEX) + .collect() +} + /// Uses the given arguments to construct a search document, then inserts it to the given index. fn add_doc( index: &mut Index, @@ -62,7 +73,7 @@ fn add_doc( doc_urls.push(url.into()); let items = items.iter().map(|&x| utils::collapse_whitespace(x.trim())); - index.add_doc(&doc_ref, items); + index.add_doc_with_tokenizer(&doc_ref, items, tokenize); } /// Renders markdown into flat unformatted text and adds it to the search index. From 00a55b35a8c58e1b270074469cb6382ad88f063c Mon Sep 17 00:00:00 2001 From: Matthew Woodcraft Date: Sun, 22 May 2022 13:57:09 +0100 Subject: [PATCH 08/21] Test that long words are omitted from the search index. Note they do appear in the 'docs' part of searchindex.json (so they will be visible in search teasers). --- tests/dummy_book/src/first/no-headers.md | 4 +++- tests/rendered_output.rs | 2 +- tests/searchindex_fixture.json | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/tests/dummy_book/src/first/no-headers.md b/tests/dummy_book/src/first/no-headers.md index 8f9a6d17..5d799aa6 100644 --- a/tests/dummy_book/src/first/no-headers.md +++ b/tests/dummy_book/src/first/no-headers.md @@ -1,3 +1,5 @@ Capybara capybara capybara. -Capybara capybara capybara. \ No newline at end of file +Capybara capybara capybara. + +ThisLongWordIsIncludedSoWeCanCheckThatSufficientlyLongWordsAreOmittedFromTheSearchIndex. diff --git a/tests/rendered_output.rs b/tests/rendered_output.rs index 873a622d..c6267830 100644 --- a/tests/rendered_output.rs +++ b/tests/rendered_output.rs @@ -772,7 +772,7 @@ mod search { ); assert_eq!( docs[&no_headers]["body"], - "Capybara capybara capybara. Capybara capybara capybara." + "Capybara capybara capybara. Capybara capybara capybara. ThisLongWordIsIncludedSoWeCanCheckThatSufficientlyLongWordsAreOmittedFromTheSearchIndex." ); } diff --git a/tests/searchindex_fixture.json b/tests/searchindex_fixture.json index 9c349b6b..3d7062d2 100644 --- a/tests/searchindex_fixture.json +++ b/tests/searchindex_fixture.json @@ -229,7 +229,7 @@ "title": "Unicode stress tests" }, "18": { - "body": "Capybara capybara capybara. Capybara capybara capybara.", + "body": "Capybara capybara capybara. Capybara capybara capybara. ThisLongWordIsIncludedSoWeCanCheckThatSufficientlyLongWordsAreOmittedFromTheSearchIndex.", "breadcrumbs": "First Chapter » No Headers", "id": "18", "title": "First Chapter" From 2b903ad057bf5860221db005f87a3e57e87b0bb2 Mon Sep 17 00:00:00 2001 From: Eric Huss Date: Wed, 1 Jun 2022 18:48:34 -0700 Subject: [PATCH 09/21] Make code block icons appear on hover. --- src/theme/css/chrome.css | 29 ++++++++++++++++++++++------- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/src/theme/css/chrome.css b/src/theme/css/chrome.css index 32b758a1..53a54c85 100644 --- a/src/theme/css/chrome.css +++ b/src/theme/css/chrome.css @@ -215,30 +215,45 @@ pre > .buttons { color: var(--sidebar-fg); cursor: pointer; + visibility: hidden; + opacity: 0; + transition: visibility 0.1s linear, opacity 0.1s linear; +} +pre:hover > .buttons { + visibility: visible; + opacity: 1 } pre > .buttons :hover { color: var(--sidebar-active); + border-color: var(--icons-hover); + background-color: var(--theme-hover); } pre > .buttons i { margin-left: 8px; } pre > .buttons button { - color: inherit; - background: transparent; - border: none; cursor: inherit; - margin: 0px; - padding: 0px 0.5rem; + margin: 0px 5px; + padding: 3px 5px; font-size: 14px; + + border-style: solid; + border-width: 1px; + border-radius: 4px; + border-color: var(--icons); + background-color: var(--theme-popup-bg); + transition: 100ms; + transition-property: color,border-color,background-color; + color: var(--icons); } @media (pointer: coarse) { pre > .buttons button { /* On mobile, make it easier to tap buttons. */ - padding: 0 1rem; + padding: 0.3rem 1rem; } } code { - padding: 1.6rem 1rem; + padding: 1rem; } /* FIXME: ACE editors overlap their buttons because ACE does absolute From 4b1a7e9ae7fc2681645553326d23454591721530 Mon Sep 17 00:00:00 2001 From: Josh Rotenberg Date: Mon, 20 Jun 2022 10:48:03 -0700 Subject: [PATCH 10/21] update warp to 0.3.2 --- Cargo.lock | 78 ++++++++++++++++++++++++------------------------------ Cargo.toml | 2 +- 2 files changed, 36 insertions(+), 44 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0446bae5..17b9748d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -382,25 +382,11 @@ dependencies = [ "new_debug_unreachable", ] -[[package]] -name = "futures" -version = "0.3.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1adc00f486adfc9ce99f77d717836f0c5aa84965eb0b4f051f4e83f7cab53f8b" -dependencies = [ - "futures-channel", - "futures-core", - "futures-io", - "futures-sink", - "futures-task", - "futures-util", -] - [[package]] name = "futures-channel" -version = "0.3.16" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74ed2411805f6e4e3d9bc904c95d5d423b89b3b25dc0250aa74729de20629ff9" +checksum = "c3083ce4b914124575708913bca19bfe887522d6e2e6d0952943f5eac4a74010" dependencies = [ "futures-core", "futures-sink", @@ -408,15 +394,9 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.16" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af51b1b4a7fdff033703db39de8802c673eb91855f2e0d47dcf3bf2c0ef01f99" - -[[package]] -name = "futures-io" -version = "0.3.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b0e06c393068f3a6ef246c75cdca793d6a46347e75286933e5e75fd2fd11582" +checksum = "0c09fd04b7e4073ac7156a9539b57a484a8ea920f79c7c675d05d289ab6110d3" [[package]] name = "futures-macro" @@ -433,9 +413,9 @@ dependencies = [ [[package]] name = "futures-sink" -version = "0.3.16" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0f30aaa67363d119812743aa5f33c201a7a66329f97d1a887022971feea4b53" +checksum = "21163e139fa306126e6eedaf49ecdb4588f939600f0b1e770f4205ee4b7fa868" [[package]] name = "futures-task" @@ -712,15 +692,6 @@ dependencies = [ "libc", ] -[[package]] -name = "input_buffer" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f97967975f448f1a7ddb12b0bc41069d09ed6a1c161a92687e057325db35d413" -dependencies = [ - "bytes", -] - [[package]] name = "iovec" version = "0.1.4" @@ -1643,6 +1614,26 @@ version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0066c8d12af8b5acd21e00547c3797fde4e8677254a7ee429176ccebbe93dd80" +[[package]] +name = "thiserror" +version = "1.0.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd829fe32373d27f76265620b5309d0340cb8550f523c1dda251d6298069069a" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0396bc89e626244658bef819e22d0cc459e795a5ebe878e6ec336d1674a8d79a" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "time" version = "0.1.43" @@ -1709,9 +1700,9 @@ dependencies = [ [[package]] name = "tokio-tungstenite" -version = "0.13.0" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1a5f475f1b9d077ea1017ecbc60890fda8e54942d680ca0b1d2b47cfa2d861b" +checksum = "511de3f85caf1c98983545490c3d09685fa8eb634e57eec22bb4db271f46cbd8" dependencies = [ "futures-util", "log", @@ -1790,19 +1781,19 @@ checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" [[package]] name = "tungstenite" -version = "0.12.0" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ada8297e8d70872fa9a551d93250a9f407beb9f37ef86494eb20012a2ff7c24" +checksum = "a0b2d8558abd2e276b0a8df5c05a2ec762609344191e5fd23e292c910e9165b5" dependencies = [ "base64", "byteorder", "bytes", "http", "httparse", - "input_buffer", "log", "rand 0.8.4", "sha-1 0.9.7", + "thiserror", "url", "utf-8", ] @@ -1905,12 +1896,13 @@ dependencies = [ [[package]] name = "warp" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "332d47745e9a0c38636dbd454729b147d16bd1ed08ae67b3ab281c4506771054" +checksum = "3cef4e1e9114a4b7f1ac799f16ce71c14de5778500c5450ec6b7b920c55b587e" dependencies = [ "bytes", - "futures", + "futures-channel", + "futures-util", "headers", "http", "hyper", diff --git a/Cargo.toml b/Cargo.toml index e3e8a32e..1f18d626 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -42,7 +42,7 @@ gitignore = { version = "1.0", optional = true } # Serve feature futures-util = { version = "0.3.4", optional = true } tokio = { version = "1", features = ["macros", "rt-multi-thread"], optional = true } -warp = { version = "0.3.1", default-features = false, features = ["websocket"], optional = true } +warp = { version = "0.3.2", default-features = false, features = ["websocket"], optional = true } # Search feature elasticlunr-rs = { version = "3.0.0", optional = true } From a5f861bf2b2dafa8b50c521ea0fb1432c749ab9d Mon Sep 17 00:00:00 2001 From: Dylan DPC <99973273+Dylan-DPC@users.noreply.github.com> Date: Wed, 22 Jun 2022 13:31:16 +0200 Subject: [PATCH 11/21] Revert "Omit words longer than 80 characters from the search index" --- src/renderer/html_handlebars/search.rs | 13 +------------ tests/dummy_book/src/first/no-headers.md | 4 +--- tests/rendered_output.rs | 2 +- tests/searchindex_fixture.json | 2 +- 4 files changed, 4 insertions(+), 17 deletions(-) diff --git a/src/renderer/html_handlebars/search.rs b/src/renderer/html_handlebars/search.rs index b39569d4..0a59ffe9 100644 --- a/src/renderer/html_handlebars/search.rs +++ b/src/renderer/html_handlebars/search.rs @@ -13,8 +13,6 @@ use crate::utils; use serde::Serialize; -const MAX_WORD_LENGTH_TO_INDEX: usize = 80; - /// Creates all files required for search. pub fn create_files(search_config: &Search, destination: &Path, book: &Book) -> Result<()> { let mut index = Index::new(&["title", "body", "breadcrumbs"]); @@ -46,15 +44,6 @@ pub fn create_files(search_config: &Search, destination: &Path, book: &Book) -> Ok(()) } -/// Tokenizes in the same way as elasticlunr-rs (for English), but also drops long tokens. -fn tokenize(text: &str) -> Vec { - text.split(|c: char| c.is_whitespace() || c == '-') - .filter(|s| !s.is_empty()) - .map(|s| s.trim().to_lowercase()) - .filter(|s| s.len() <= MAX_WORD_LENGTH_TO_INDEX) - .collect() -} - /// Uses the given arguments to construct a search document, then inserts it to the given index. fn add_doc( index: &mut Index, @@ -73,7 +62,7 @@ fn add_doc( doc_urls.push(url.into()); let items = items.iter().map(|&x| utils::collapse_whitespace(x.trim())); - index.add_doc_with_tokenizer(&doc_ref, items, tokenize); + index.add_doc(&doc_ref, items); } /// Renders markdown into flat unformatted text and adds it to the search index. diff --git a/tests/dummy_book/src/first/no-headers.md b/tests/dummy_book/src/first/no-headers.md index 5d799aa6..8f9a6d17 100644 --- a/tests/dummy_book/src/first/no-headers.md +++ b/tests/dummy_book/src/first/no-headers.md @@ -1,5 +1,3 @@ Capybara capybara capybara. -Capybara capybara capybara. - -ThisLongWordIsIncludedSoWeCanCheckThatSufficientlyLongWordsAreOmittedFromTheSearchIndex. +Capybara capybara capybara. \ No newline at end of file diff --git a/tests/rendered_output.rs b/tests/rendered_output.rs index c6267830..873a622d 100644 --- a/tests/rendered_output.rs +++ b/tests/rendered_output.rs @@ -772,7 +772,7 @@ mod search { ); assert_eq!( docs[&no_headers]["body"], - "Capybara capybara capybara. Capybara capybara capybara. ThisLongWordIsIncludedSoWeCanCheckThatSufficientlyLongWordsAreOmittedFromTheSearchIndex." + "Capybara capybara capybara. Capybara capybara capybara." ); } diff --git a/tests/searchindex_fixture.json b/tests/searchindex_fixture.json index 3d7062d2..9c349b6b 100644 --- a/tests/searchindex_fixture.json +++ b/tests/searchindex_fixture.json @@ -229,7 +229,7 @@ "title": "Unicode stress tests" }, "18": { - "body": "Capybara capybara capybara. Capybara capybara capybara. ThisLongWordIsIncludedSoWeCanCheckThatSufficientlyLongWordsAreOmittedFromTheSearchIndex.", + "body": "Capybara capybara capybara. Capybara capybara capybara.", "breadcrumbs": "First Chapter » No Headers", "id": "18", "title": "First Chapter" From 1f8c090a5f0797e6b0d593ea43ce9e1e71de51e6 Mon Sep 17 00:00:00 2001 From: Matthew Woodcraft Date: Sun, 22 May 2022 13:37:19 +0100 Subject: [PATCH 12/21] When creating the search index, omit words longer than 80 characters This avoids creating deeply nested objects in searchindex.json --- src/renderer/html_handlebars/search.rs | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/src/renderer/html_handlebars/search.rs b/src/renderer/html_handlebars/search.rs index 0a59ffe9..b5f8c4a2 100644 --- a/src/renderer/html_handlebars/search.rs +++ b/src/renderer/html_handlebars/search.rs @@ -2,7 +2,7 @@ use std::borrow::Cow; use std::collections::{HashMap, HashSet}; use std::path::Path; -use elasticlunr::Index; +use elasticlunr::{Index, IndexBuilder}; use pulldown_cmark::*; use crate::book::{Book, BookItem}; @@ -13,9 +13,25 @@ use crate::utils; use serde::Serialize; +const MAX_WORD_LENGTH_TO_INDEX: usize = 80; + +/// Tokenizes in the same way as elasticlunr-rs (for English), but also drops long tokens. +fn tokenize(text: &str) -> Vec { + text.split(|c: char| c.is_whitespace() || c == '-') + .filter(|s| !s.is_empty()) + .map(|s| s.trim().to_lowercase()) + .filter(|s| s.len() <= MAX_WORD_LENGTH_TO_INDEX) + .collect() +} + /// Creates all files required for search. pub fn create_files(search_config: &Search, destination: &Path, book: &Book) -> Result<()> { - let mut index = Index::new(&["title", "body", "breadcrumbs"]); + let mut index = IndexBuilder::new() + .add_field_with_tokenizer("title", Box::new(&tokenize)) + .add_field_with_tokenizer("body", Box::new(&tokenize)) + .add_field_with_tokenizer("breadcrumbs", Box::new(&tokenize)) + .build(); + let mut doc_urls = Vec::with_capacity(book.sections.len()); for item in book.iter() { From 000a93dc777fe856bd93ccb29616250d05f99c67 Mon Sep 17 00:00:00 2001 From: Matthew Woodcraft Date: Sun, 22 May 2022 13:57:09 +0100 Subject: [PATCH 13/21] Test that long words are omitted from the search index. Note they do appear in the 'docs' part of searchindex.json (so they will be visible in search teasers). --- tests/dummy_book/src/first/no-headers.md | 4 +++- tests/rendered_output.rs | 2 +- tests/searchindex_fixture.json | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/tests/dummy_book/src/first/no-headers.md b/tests/dummy_book/src/first/no-headers.md index 8f9a6d17..5d799aa6 100644 --- a/tests/dummy_book/src/first/no-headers.md +++ b/tests/dummy_book/src/first/no-headers.md @@ -1,3 +1,5 @@ Capybara capybara capybara. -Capybara capybara capybara. \ No newline at end of file +Capybara capybara capybara. + +ThisLongWordIsIncludedSoWeCanCheckThatSufficientlyLongWordsAreOmittedFromTheSearchIndex. diff --git a/tests/rendered_output.rs b/tests/rendered_output.rs index 873a622d..c6267830 100644 --- a/tests/rendered_output.rs +++ b/tests/rendered_output.rs @@ -772,7 +772,7 @@ mod search { ); assert_eq!( docs[&no_headers]["body"], - "Capybara capybara capybara. Capybara capybara capybara." + "Capybara capybara capybara. Capybara capybara capybara. ThisLongWordIsIncludedSoWeCanCheckThatSufficientlyLongWordsAreOmittedFromTheSearchIndex." ); } diff --git a/tests/searchindex_fixture.json b/tests/searchindex_fixture.json index 9c349b6b..3d7062d2 100644 --- a/tests/searchindex_fixture.json +++ b/tests/searchindex_fixture.json @@ -229,7 +229,7 @@ "title": "Unicode stress tests" }, "18": { - "body": "Capybara capybara capybara. Capybara capybara capybara.", + "body": "Capybara capybara capybara. Capybara capybara capybara. ThisLongWordIsIncludedSoWeCanCheckThatSufficientlyLongWordsAreOmittedFromTheSearchIndex.", "breadcrumbs": "First Chapter » No Headers", "id": "18", "title": "First Chapter" From 1d89127d8f999877770ef383fdb2193c39388e0c Mon Sep 17 00:00:00 2001 From: Eric Huss Date: Mon, 27 Jun 2022 11:10:09 -0700 Subject: [PATCH 14/21] Mention how to uninstall. Closes #1822. --- guide/src/guide/installation.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/guide/src/guide/installation.md b/guide/src/guide/installation.md index d7946587..128b1047 100644 --- a/guide/src/guide/installation.md +++ b/guide/src/guide/installation.md @@ -30,6 +30,8 @@ cargo install mdbook This will automatically download mdBook from [crates.io], build it, and install it in Cargo's global binary directory (`~/.cargo/bin/` by default). +To uninstall, run the command `cargo uninstall mdbook`. + [Rust installation page]: https://www.rust-lang.org/tools/install [crates.io]: https://crates.io/ From ddf71222c5bdf20100b8fb886ff1b634f979a9d6 Mon Sep 17 00:00:00 2001 From: Eric Huss Date: Mon, 27 Jun 2022 12:05:37 -0700 Subject: [PATCH 15/21] Bump tokio from 1.10.0 to 1.16.1 --- Cargo.lock | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 17b9748d..b0e263b1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1661,11 +1661,10 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" [[package]] name = "tokio" -version = "1.10.0" +version = "1.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01cf844b23c6131f624accf65ce0e4e9956a8bb329400ea5bcc26ae3a5c20b0b" +checksum = "0c27a64b625de6d309e8c57716ba93021dccf1b3b5c97edd6d3dd2d2135afc0a" dependencies = [ - "autocfg", "bytes", "libc", "memchr", @@ -1678,9 +1677,9 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "1.3.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54473be61f4ebe4efd09cec9bd5d16fa51d70ea0192213d754d2d500457db110" +checksum = "9724f9a975fb987ef7a3cd9be0350edcbe130698af5b8f7a631e23d42d052484" dependencies = [ "proc-macro2", "quote", From 494e6722b28d81ee6ede6dca90a16a47c582ffac Mon Sep 17 00:00:00 2001 From: Eric Huss Date: Mon, 27 Jun 2022 12:50:47 -0700 Subject: [PATCH 16/21] Update env_logger from 0.7.1 to 0.9.0 This drops quick-error 1.2.3 from the tree --- Cargo.lock | 19 +++++-------------- Cargo.toml | 2 +- 2 files changed, 6 insertions(+), 15 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b0e263b1..70d90163 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -283,9 +283,9 @@ dependencies = [ [[package]] name = "env_logger" -version = "0.7.1" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44533bbbb3bb3c1fa17d9f2e4e38bbbaf8396ba82193c4cb1b6445d711445d36" +checksum = "0b2cf0344971ee6c64c31be0d530793fba457d322dfec2810c453d0ef228f9c3" dependencies = [ "atty", "humantime", @@ -525,7 +525,7 @@ dependencies = [ "log", "pest", "pest_derive", - "quick-error 2.0.1", + "quick-error", "serde", "serde_json", ] @@ -620,12 +620,9 @@ checksum = "6456b8a6c8f33fee7d958fcd1b60d55b11940a79e63ae87013e6d22e26034440" [[package]] name = "humantime" -version = "1.3.0" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f" -dependencies = [ - "quick-error 1.2.3", -] +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" [[package]] name = "hyper" @@ -1245,12 +1242,6 @@ dependencies = [ "unicase", ] -[[package]] -name = "quick-error" -version = "1.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" - [[package]] name = "quick-error" version = "2.0.1" diff --git a/Cargo.toml b/Cargo.toml index 1f18d626..98dc62c0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,7 +20,7 @@ anyhow = "1.0.28" chrono = "0.4" clap = { version = "3.0", features = ["cargo"] } clap_complete = "3.0" -env_logger = "0.7.1" +env_logger = "0.9.0" handlebars = "4.0" lazy_static = "1.0" log = "0.4" From 2c2ba636a9daa1e5f1389fe3c92deafef42acb4f Mon Sep 17 00:00:00 2001 From: Eric Huss Date: Mon, 27 Jun 2022 13:20:09 -0700 Subject: [PATCH 17/21] Update pretty_assertions from 0.6.1 to 1.2.1 --- Cargo.lock | 16 ++++++++-------- Cargo.toml | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 70d90163..82dfa8c6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -28,9 +28,9 @@ dependencies = [ [[package]] name = "ansi_term" -version = "0.11.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" +checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" dependencies = [ "winapi 0.3.9", ] @@ -228,10 +228,10 @@ dependencies = [ ] [[package]] -name = "difference" -version = "2.0.0" +name = "diff" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "524cbf6897b527295dff137cec09ecf3a05f4fddffd7dfcd1585403449e74198" +checksum = "0e25ea47919b1560c4e3b7fe0aaab9becf5b84a10325ddf7db0f0ba5e1026499" [[package]] name = "difflib" @@ -1200,13 +1200,13 @@ dependencies = [ [[package]] name = "pretty_assertions" -version = "0.6.1" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f81e1644e1b54f5a68959a29aa86cde704219254669da328ecfdf6a1f09d427" +checksum = "c89f989ac94207d048d92db058e4f6ec7342b0971fc58d1271ca148b799b3563" dependencies = [ "ansi_term", "ctor", - "difference", + "diff", "output_vt100", ] diff --git a/Cargo.toml b/Cargo.toml index 98dc62c0..0b54b5ab 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -53,7 +53,7 @@ assert_cmd = "1" predicates = "2" select = "0.5" semver = "1.0" -pretty_assertions = "0.6" +pretty_assertions = "1.2.1" walkdir = "2.0" [features] From 248863addf8396bcec0c0bf723a6532280179ce9 Mon Sep 17 00:00:00 2001 From: ISSOtm Date: Sun, 26 Jun 2022 11:37:52 +0200 Subject: [PATCH 18/21] Fix Clippy lints Also remove `allow(clippy::*)`s where possible --- src/book/mod.rs | 12 +++--- src/cmd/init.rs | 5 +-- src/config.rs | 34 +++++++---------- src/lib.rs | 1 - src/preprocess/links.rs | 1 + src/renderer/html_handlebars/hbs_renderer.rs | 2 +- .../html_handlebars/helpers/navigation.rs | 6 +-- src/renderer/html_handlebars/helpers/toc.rs | 38 ++++++++++--------- src/renderer/html_handlebars/search.rs | 13 ++++--- src/utils/string.rs | 2 + 10 files changed, 54 insertions(+), 60 deletions(-) diff --git a/src/book/mod.rs b/src/book/mod.rs index 3370d92c..9745d2b7 100644 --- a/src/book/mod.rs +++ b/src/book/mod.rs @@ -386,7 +386,7 @@ fn determine_renderers(config: &Config) -> Vec> { renderers } -const DEFAULT_PREPROCESSORS: &[&'static str] = &["links", "index"]; +const DEFAULT_PREPROCESSORS: &[&str] = &["links", "index"]; fn is_default_preprocessor(pre: &dyn Preprocessor) -> bool { let name = pre.name(); @@ -756,10 +756,9 @@ mod tests { let preprocessors = determine_preprocessors(&cfg).unwrap(); - assert!(preprocessors + assert!(!preprocessors .iter() - .find(|preprocessor| preprocessor.name() == "random") - .is_none()); + .any(|preprocessor| preprocessor.name() == "random")); } #[test] @@ -776,10 +775,9 @@ mod tests { let preprocessors = determine_preprocessors(&cfg).unwrap(); - assert!(preprocessors + assert!(!preprocessors .iter() - .find(|preprocessor| preprocessor.name() == "links") - .is_none()); + .any(|preprocessor| preprocessor.name() == "links")); } #[test] diff --git a/src/cmd/init.rs b/src/cmd/init.rs index 1ee5ff21..c964dcc1 100644 --- a/src/cmd/init.rs +++ b/src/cmd/init.rs @@ -122,8 +122,5 @@ fn confirm() -> bool { io::stdout().flush().unwrap(); let mut s = String::new(); io::stdin().read_line(&mut s).ok(); - match &*s.trim() { - "Y" | "y" | "yes" | "Yes" => true, - _ => false, - } + matches!(&*s.trim(), "Y" | "y" | "yes" | "Yes") } diff --git a/src/config.rs b/src/config.rs index 951957bd..b7d03d1a 100644 --- a/src/config.rs +++ b/src/config.rs @@ -227,10 +227,10 @@ impl Config { let value = Value::try_from(value) .with_context(|| "Unable to represent the item as a JSON Value")?; - if index.starts_with("book.") { - self.book.update_value(&index[5..], value); - } else if index.starts_with("build.") { - self.build.update_value(&index[6..], value); + if let Some(key) = index.strip_prefix("book.") { + self.book.update_value(key, value); + } else if let Some(key) = index.strip_prefix("build.") { + self.build.update_value(key, value); } else { self.rest.insert(index, value); } @@ -371,15 +371,8 @@ impl Serialize for Config { } fn parse_env(key: &str) -> Option { - const PREFIX: &str = "MDBOOK_"; - - if key.starts_with(PREFIX) { - let key = &key[PREFIX.len()..]; - - Some(key.to_lowercase().replace("__", ".").replace("_", "-")) - } else { - None - } + key.strip_prefix("MDBOOK_") + .map(|key| key.to_lowercase().replace("__", ".").replace('_', "-")) } fn is_legacy_format(table: &Value) -> bool { @@ -828,7 +821,7 @@ mod tests { "#; let got = Config::from_str(src).unwrap(); - assert_eq!(got.html_config().unwrap().playground.runnable, false); + assert!(!got.html_config().unwrap().playground.runnable); } #[test] @@ -1037,7 +1030,7 @@ mod tests { fn encode_env_var(key: &str) -> String { format!( "MDBOOK_{}", - key.to_uppercase().replace('.', "__").replace("-", "_") + key.to_uppercase().replace('.', "__").replace('-', "_") ) } @@ -1061,11 +1054,10 @@ mod tests { } #[test] - #[allow(clippy::approx_constant)] fn update_config_using_env_var_and_complex_value() { let mut cfg = Config::default(); let key = "foo-bar.baz"; - let value = json!({"array": [1, 2, 3], "number": 3.14}); + let value = json!({"array": [1, 2, 3], "number": 13.37}); let value_str = serde_json::to_string(&value).unwrap(); assert!(cfg.get(key).is_none()); @@ -1184,15 +1176,15 @@ mod tests { "#; let got = Config::from_str(src).unwrap(); let html_config = got.html_config().unwrap(); - assert_eq!(html_config.print.enable, false); - assert_eq!(html_config.print.page_break, true); + assert!(!html_config.print.enable); + assert!(html_config.print.page_break); let src = r#" [output.html.print] page-break = false "#; let got = Config::from_str(src).unwrap(); let html_config = got.html_config().unwrap(); - assert_eq!(html_config.print.enable, true); - assert_eq!(html_config.print.page_break, false); + assert!(html_config.print.enable); + assert!(!html_config.print.page_break); } } diff --git a/src/lib.rs b/src/lib.rs index 23309fb0..cc62b0ab 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -82,7 +82,6 @@ #![deny(missing_docs)] #![deny(rust_2018_idioms)] -#![allow(clippy::comparison_chain)] #[macro_use] extern crate lazy_static; diff --git a/src/preprocess/links.rs b/src/preprocess/links.rs index edd97ba9..7ca6fd34 100644 --- a/src/preprocess/links.rs +++ b/src/preprocess/links.rs @@ -146,6 +146,7 @@ enum RangeOrAnchor { } // A range of lines specified with some include directive. +#[allow(clippy::enum_variant_names)] // The prefix can't be removed, and is meant to mirror the contained type #[derive(PartialEq, Debug, Clone)] enum LineRange { Range(Range), diff --git a/src/renderer/html_handlebars/hbs_renderer.rs b/src/renderer/html_handlebars/hbs_renderer.rs index 8a13db9f..52d0ab65 100644 --- a/src/renderer/html_handlebars/hbs_renderer.rs +++ b/src/renderer/html_handlebars/hbs_renderer.rs @@ -814,7 +814,7 @@ fn fix_code_blocks(html: &str) -> String { FIX_CODE_BLOCKS .replace_all(html, |caps: &Captures<'_>| { let before = &caps[1]; - let classes = &caps[2].replace(",", " "); + let classes = &caps[2].replace(',', " "); let after = &caps[3]; format!( diff --git a/src/renderer/html_handlebars/helpers/navigation.rs b/src/renderer/html_handlebars/helpers/navigation.rs index 83bdadb3..d3f6ca90 100644 --- a/src/renderer/html_handlebars/helpers/navigation.rs +++ b/src/renderer/html_handlebars/helpers/navigation.rs @@ -61,7 +61,7 @@ fn find_chapter( .as_json() .as_str() .ok_or_else(|| RenderError::new("Type error for `path`, string expected"))? - .replace("\"", ""); + .replace('\"', ""); if !rc.evaluate(ctx, "@root/is_index")?.is_missing() { // Special case for index.md which may be a synthetic page. @@ -121,7 +121,7 @@ fn render( .as_json() .as_str() .ok_or_else(|| RenderError::new("Type error for `path`, string expected"))? - .replace("\"", ""); + .replace('\"', ""); context.insert( "path_to_root".to_owned(), @@ -141,7 +141,7 @@ fn render( .with_extension("html") .to_str() .ok_or_else(|| RenderError::new("Link could not be converted to str")) - .map(|p| context.insert("link".to_owned(), json!(p.replace("\\", "/")))) + .map(|p| context.insert("link".to_owned(), json!(p.replace('\\', "/")))) })?; trace!("Render template"); diff --git a/src/renderer/html_handlebars/helpers/toc.rs b/src/renderer/html_handlebars/helpers/toc.rs index 5869dd36..6ae62aa7 100644 --- a/src/renderer/html_handlebars/helpers/toc.rs +++ b/src/renderer/html_handlebars/helpers/toc.rs @@ -1,5 +1,5 @@ -use std::collections::BTreeMap; use std::path::Path; +use std::{cmp::Ordering, collections::BTreeMap}; use crate::utils; use crate::utils::bracket_escape; @@ -33,7 +33,7 @@ impl HelperDef for RenderToc { .as_json() .as_str() .ok_or_else(|| RenderError::new("Type error for `path`, string expected"))? - .replace("\"", ""); + .replace('\"', ""); let current_section = rc .evaluate(ctx, "@root/section")? @@ -81,22 +81,26 @@ impl HelperDef for RenderToc { level - 1 < fold_level as usize }; - if level > current_level { - while level > current_level { - out.write("
  • ")?; - out.write("
      ")?; - current_level += 1; + match level.cmp(¤t_level) { + Ordering::Greater => { + while level > current_level { + out.write("
    1. ")?; + out.write("
        ")?; + current_level += 1; + } + write_li_open_tag(out, is_expanded, false)?; } - write_li_open_tag(out, is_expanded, false)?; - } else if level < current_level { - while level < current_level { - out.write("
      ")?; - out.write("
    2. ")?; - current_level -= 1; + Ordering::Less => { + while level < current_level { + out.write("
    ")?; + out.write("
  • ")?; + current_level -= 1; + } + write_li_open_tag(out, is_expanded, false)?; + } + Ordering::Equal => { + write_li_open_tag(out, is_expanded, item.get("section").is_none())?; } - write_li_open_tag(out, is_expanded, false)?; - } else { - write_li_open_tag(out, is_expanded, item.get("section").is_none())?; } // Part title @@ -119,7 +123,7 @@ impl HelperDef for RenderToc { .to_str() .unwrap() // Hack for windows who tends to use `\` as separator instead of `/` - .replace("\\", "/"); + .replace('\\', "/"); // Add link out.write(&utils::fs::path_to_root(¤t_path))?; diff --git a/src/renderer/html_handlebars/search.rs b/src/renderer/html_handlebars/search.rs index 0a59ffe9..fb188fb0 100644 --- a/src/renderer/html_handlebars/search.rs +++ b/src/renderer/html_handlebars/search.rs @@ -211,12 +211,13 @@ fn write_to_json(index: Index, search_config: &Search, doc_urls: Vec) -> let mut fields = BTreeMap::new(); let mut opt = SearchOptionsField::default(); - opt.boost = Some(search_config.boost_title); - fields.insert("title".into(), opt); - opt.boost = Some(search_config.boost_paragraph); - fields.insert("body".into(), opt); - opt.boost = Some(search_config.boost_hierarchy); - fields.insert("breadcrumbs".into(), opt); + let mut insert_boost = |key: &str, boost| { + opt.boost = Some(boost); + fields.insert(key.into(), opt); + }; + insert_boost("title", search_config.boost_title); + insert_boost("body", search_config.boost_paragraph); + insert_boost("breadcrumbs", search_config.boost_hierarchy); let search_options = SearchOptions { bool: if search_config.use_boolean_and { diff --git a/src/utils/string.rs b/src/utils/string.rs index 59931743..97485d7b 100644 --- a/src/utils/string.rs +++ b/src/utils/string.rs @@ -122,6 +122,7 @@ mod tests { }; #[test] + #[allow(clippy::reversed_empty_ranges)] // Intentionally checking that those are correctly handled fn take_lines_test() { let s = "Lorem\nipsum\ndolor\nsit\namet"; assert_eq!(take_lines(s, 1..3), "ipsum\ndolor"); @@ -163,6 +164,7 @@ mod tests { } #[test] + #[allow(clippy::reversed_empty_ranges)] // Intentionally checking that those are correctly handled fn take_rustdoc_include_lines_test() { let s = "Lorem\nipsum\ndolor\nsit\namet"; assert_eq!( From 857188392348cb07f3300d6c3db7302f7adfca88 Mon Sep 17 00:00:00 2001 From: ISSOtm Date: Wed, 22 Jun 2022 20:58:47 +0200 Subject: [PATCH 19/21] Mark the first chapter as "index", even if not the first book item --- src/renderer/html_handlebars/hbs_renderer.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/renderer/html_handlebars/hbs_renderer.rs b/src/renderer/html_handlebars/hbs_renderer.rs index 26f1432c..8aebbce8 100644 --- a/src/renderer/html_handlebars/hbs_renderer.rs +++ b/src/renderer/html_handlebars/hbs_renderer.rs @@ -540,7 +540,8 @@ impl Renderer for HtmlHandlebars { chapter_titles: &ctx.chapter_titles, }; self.render_item(item, ctx, &mut print_content)?; - is_index = false; + // Only the first non-draft chapter item should be treated as the "index" + is_index &= !matches!(item, BookItem::Chapter(ch) if !ch.is_draft_chapter()); } // Render 404 page From a91e8885755bfc1f39a9b2edd7af0d37cd89b4ee Mon Sep 17 00:00:00 2001 From: ISSOtm Date: Wed, 22 Jun 2022 22:55:52 +0200 Subject: [PATCH 20/21] Add test for index page --- tests/dummy_book/index_html_test/SUMMARY.md | 11 +++++++ tests/dummy_book/index_html_test/chapter_1.md | 1 + tests/rendered_output.rs | 30 ++++++++++++++++++- 3 files changed, 41 insertions(+), 1 deletion(-) create mode 100644 tests/dummy_book/index_html_test/SUMMARY.md create mode 100644 tests/dummy_book/index_html_test/chapter_1.md diff --git a/tests/dummy_book/index_html_test/SUMMARY.md b/tests/dummy_book/index_html_test/SUMMARY.md new file mode 100644 index 00000000..37bf68cd --- /dev/null +++ b/tests/dummy_book/index_html_test/SUMMARY.md @@ -0,0 +1,11 @@ +# Summary + +--- + +- [None of these should be treated as the "index chapter"]() + +# Part 1 + +- [Not this either]() +- [Chapter 1](./chapter_1.md) +- [And not this]() diff --git a/tests/dummy_book/index_html_test/chapter_1.md b/tests/dummy_book/index_html_test/chapter_1.md new file mode 100644 index 00000000..b743fda3 --- /dev/null +++ b/tests/dummy_book/index_html_test/chapter_1.md @@ -0,0 +1 @@ +# Chapter 1 diff --git a/tests/rendered_output.rs b/tests/rendered_output.rs index c6267830..24d3427b 100644 --- a/tests/rendered_output.rs +++ b/tests/rendered_output.rs @@ -15,7 +15,7 @@ use select::predicate::{Class, Name, Predicate}; use std::collections::HashMap; use std::ffi::OsStr; use std::fs; -use std::io::Write; +use std::io::{Read, Write}; use std::path::{Component, Path, PathBuf}; use std::str::FromStr; use tempfile::Builder as TempFileBuilder; @@ -467,6 +467,34 @@ fn by_default_mdbook_use_index_preprocessor_to_convert_readme_to_index() { assert_doesnt_contain_strings(&second_index, &unexpected_strings); } +#[test] +fn first_chapter_is_copied_as_index_even_if_not_first_elem() { + let temp = DummyBook::new().build().unwrap(); + let mut cfg = Config::default(); + cfg.set("book.src", "index_html_test") + .expect("Couldn't set config.book.src to \"index_html_test\""); + let md = MDBook::load_with_config(temp.path(), cfg).unwrap(); + md.build().unwrap(); + + // In theory, just reading the entire files into memory and comparing is sufficient for *testing*, + // but since the files are temporary and get deleted when the test completes, we'll want to print + // the differences on failure. + // We could invoke `diff` on the files on failure, but that may not be portable (hi, Windows...) + // so we'll do the job ourselves—potentially a bit sloppily, but that can always be piped into + // `diff` manually afterwards. + let book_path = temp.path().join("book"); + let read_file = |path: &str| { + let mut buf = String::new(); + fs::File::open(book_path.join(path)) + .with_context(|| format!("Failed to read {}", path)) + .unwrap() + .read_to_string(&mut buf) + .unwrap(); + buf + }; + pretty_assertions::assert_eq!(read_file("chapter_1.html"), read_file("index.html")); +} + #[test] fn theme_dir_overrides_work_correctly() { let book_dir = dummy_book::new_copy_of_example_book().unwrap(); From fa5f32c7fdc9ff74ac9aee633505083fdabab62c Mon Sep 17 00:00:00 2001 From: ISSOtm Date: Wed, 22 Jun 2022 23:40:36 +0200 Subject: [PATCH 21/21] Make link to first chapter active in index page Makes both pages more consistent, and also the previous test pass Co-authored-by: Eric Huss --- src/renderer/html_handlebars/hbs_renderer.rs | 2 +- src/renderer/html_handlebars/helpers/toc.rs | 8 ++++++- tests/rendered_output.rs | 23 +++++--------------- 3 files changed, 13 insertions(+), 20 deletions(-) diff --git a/src/renderer/html_handlebars/hbs_renderer.rs b/src/renderer/html_handlebars/hbs_renderer.rs index 8aebbce8..b933a359 100644 --- a/src/renderer/html_handlebars/hbs_renderer.rs +++ b/src/renderer/html_handlebars/hbs_renderer.rs @@ -116,7 +116,7 @@ impl HtmlHandlebars { if ctx.is_index { ctx.data.insert("path".to_owned(), json!("index.md")); ctx.data.insert("path_to_root".to_owned(), json!("")); - ctx.data.insert("is_index".to_owned(), json!("true")); + ctx.data.insert("is_index".to_owned(), json!(true)); let rendered_index = ctx.handlebars.render("index", &ctx.data)?; let rendered_index = self.post_process(rendered_index, &ctx.html_config.playground, ctx.edition); diff --git a/src/renderer/html_handlebars/helpers/toc.rs b/src/renderer/html_handlebars/helpers/toc.rs index 6ae62aa7..0884d30a 100644 --- a/src/renderer/html_handlebars/helpers/toc.rs +++ b/src/renderer/html_handlebars/helpers/toc.rs @@ -57,6 +57,11 @@ impl HelperDef for RenderToc { out.write("
      ")?; let mut current_level = 1; + // The "index" page, which has this attribute set, is supposed to alias the first chapter in + // the book, i.e. the first link. There seems to be no easy way to determine which chapter + // the "index" is aliasing from within the renderer, so this is used instead to force the + // first link to be active. See further below. + let mut is_first_chapter = ctx.data().get("is_index").is_some(); for item in chapters { // Spacer @@ -130,7 +135,8 @@ impl HelperDef for RenderToc { out.write(&tmp)?; out.write("\"")?; - if path == ¤t_path { + if path == ¤t_path || is_first_chapter { + is_first_chapter = false; out.write(" class=\"active\"")?; } diff --git a/tests/rendered_output.rs b/tests/rendered_output.rs index 24d3427b..9750a35e 100644 --- a/tests/rendered_output.rs +++ b/tests/rendered_output.rs @@ -15,7 +15,7 @@ use select::predicate::{Class, Name, Predicate}; use std::collections::HashMap; use std::ffi::OsStr; use std::fs; -use std::io::{Read, Write}; +use std::io::Write; use std::path::{Component, Path, PathBuf}; use std::str::FromStr; use tempfile::Builder as TempFileBuilder; @@ -476,23 +476,10 @@ fn first_chapter_is_copied_as_index_even_if_not_first_elem() { let md = MDBook::load_with_config(temp.path(), cfg).unwrap(); md.build().unwrap(); - // In theory, just reading the entire files into memory and comparing is sufficient for *testing*, - // but since the files are temporary and get deleted when the test completes, we'll want to print - // the differences on failure. - // We could invoke `diff` on the files on failure, but that may not be portable (hi, Windows...) - // so we'll do the job ourselves—potentially a bit sloppily, but that can always be piped into - // `diff` manually afterwards. - let book_path = temp.path().join("book"); - let read_file = |path: &str| { - let mut buf = String::new(); - fs::File::open(book_path.join(path)) - .with_context(|| format!("Failed to read {}", path)) - .unwrap() - .read_to_string(&mut buf) - .unwrap(); - buf - }; - pretty_assertions::assert_eq!(read_file("chapter_1.html"), read_file("index.html")); + let root = temp.path().join("book"); + let chapter = fs::read_to_string(root.join("chapter_1.html")).expect("read chapter 1"); + let index = fs::read_to_string(root.join("index.html")).expect("read index"); + pretty_assertions::assert_eq!(chapter, index); } #[test]