diff --git a/crates/mdbook-html/front-end/css/general.css b/crates/mdbook-html/front-end/css/general.css index 91401e3bb2..cf931876b2 100644 --- a/crates/mdbook-html/front-end/css/general.css +++ b/crates/mdbook-html/front-end/css/general.css @@ -406,3 +406,35 @@ dd > p { /* Add some space between the icon and the text. */ margin-right: 8px; } + +.content .checkbox-img, .checkbox-label > .img-wrapper { + display: none; +} +.content .checkbox-label { + display: block; + max-width: 100%; +} +.content .checkbox-label img { + cursor: zoom-in; +} +.content .checkbox-img:checked + .checkbox-label > .img-wrapper { + position: fixed; + top: 0; + left: 0; + width: 100vw; + height: 100vh; + background-color: rgba(0, 0, 0, 0.4); + z-index: 1001; + display: flex; + align-items: center; + justify-content: center; + cursor: zoom-out; +} +.content .checkbox-img:checked + .checkbox-label > .img-wrapper img { + --img-padding: 5px; + padding: var(--img-padding); + background: #999; + cursor: zoom-out; + max-width: calc(100% - (var(--img-padding) * 2)); + max-height: calc(100% - (var(--img-padding) * 2)); +} diff --git a/crates/mdbook-html/front-end/js/book.js b/crates/mdbook-html/front-end/js/book.js index fe74b7106f..3790ccd8a8 100644 --- a/crates/mdbook-html/front-end/js/book.js +++ b/crates/mdbook-html/front-end/js/book.js @@ -658,6 +658,12 @@ aria-label="Show hidden lines">'; })(); (function chapterNavigation() { + function zoomOutImages() { + for (const elem of Array.from(document.querySelectorAll('input.checkbox-img'))) { + elem.checked = false; + } + } + document.addEventListener('keydown', function(e) { if (e.altKey || e.ctrlKey || @@ -724,6 +730,9 @@ aria-label="Show hidden lines">'; e.preventDefault(); showHelp(); break; + case 'Escape': + zoomOutImages(); + break; } // Rest of the keys are only active when the Shift key is not pressed diff --git a/crates/mdbook-html/src/html/tree.rs b/crates/mdbook-html/src/html/tree.rs index 5cb97ce378..99b787ae8d 100644 --- a/crates/mdbook-html/src/html/tree.rs +++ b/crates/mdbook-html/src/html/tree.rs @@ -9,7 +9,7 @@ use super::tokenizer::parse_html; use super::{HtmlRenderOptions, hide_lines, wrap_rust_main}; use crate::utils::{id_from_content, unique_id}; use ego_tree::{NodeId, NodeRef, Tree}; -use html5ever::tendril::StrTendril; +use html5ever::tendril::{SliceExt, StrTendril}; use html5ever::tokenizer::{TagKind, Token}; use html5ever::{LocalName, QualName}; use indexmap::IndexMap; @@ -69,7 +69,7 @@ impl Node { } /// An HTML element. -#[derive(Debug)] +#[derive(Debug, Clone)] pub(crate) struct Element { /// The tag name. pub(crate) name: QualName, @@ -199,6 +199,8 @@ pub(crate) struct MarkdownTreeBuilder<'opts, 'event, EventIter> { /// tag. After the document has been parsed, all the definitions are moved /// to the end of the document. footnote_defs: HashMap, NodeId>, + /// Current ID to be used to generate the images label and checkbox. + img_label_id: usize, } impl<'opts, 'event, EventIter> MarkdownTreeBuilder<'opts, 'event, EventIter> @@ -222,6 +224,7 @@ where table_cell_index: 0, footnote_numbers: HashMap::new(), footnote_defs: HashMap::new(), + img_label_id: 0, }; builder.process_events(); builder.add_header_links(); @@ -230,6 +233,13 @@ where builder.tree } + /// Returns the current `img_label_id` and increase its value. + fn get_current_img_label_id(&mut self) -> usize { + let ret = self.img_label_id; + self.img_label_id += 1; + ret + } + /// Append a new child to the current node. /// /// Returns the [`NodeId`] of the new node. @@ -557,16 +567,39 @@ where title, id: _, } => { + let img_input_id = format!("checkbox-img-{}", self.get_current_img_label_id()); + + let mut input = Element::new("input"); + input.insert_attr("id", img_input_id.to_tendril()); + input.insert_attr("class", "checkbox-img".to_tendril()); + input.insert_attr("type", "checkbox".to_tendril()); + self.append(Node::Element(input)); + + let mut label = Element::new("label"); + label.insert_attr("class", "checkbox-label".to_tendril()); + label.insert_attr("for", img_input_id.to_tendril()); + self.push(Node::Element(label)); + let mut img = Element::new("img"); let src = fix_link(dest_url).into_tendril(); img.insert_attr("src", src); + // This will eat TagEnd::Image + let alt = self.text_for_img_alt(); + img.insert_attr("alt", alt.to_tendril()); if !title.is_empty() { img.insert_attr("title", title.into_tendril()); + } else { + img.insert_attr("title", alt.into()); } - // This will eat TagEnd::Image - let alt = self.text_for_img_alt(); - img.insert_attr("alt", alt.into()); + self.append(Node::Element(img.clone())); + + let mut wrapper = Element::new("span"); + wrapper.insert_attr("class", "img-wrapper".to_tendril()); + self.push_no_stack(Node::Element(wrapper)); self.append(Node::Element(img)); + + // We exit the `label` and the `div`. + self.pop(); return; } Tag::MetadataBlock(_) => { diff --git a/tests/gui/books/basic/src/chapter_1.md b/tests/gui/books/basic/src/chapter_1.md index b743fda354..b19dc20616 100644 --- a/tests/gui/books/basic/src/chapter_1.md +++ b/tests/gui/books/basic/src/chapter_1.md @@ -1 +1,3 @@ # Chapter 1 + +![rust logo](rust-logo.svg) diff --git a/tests/gui/books/basic/src/rust-logo.svg b/tests/gui/books/basic/src/rust-logo.svg new file mode 100644 index 0000000000..1a6c762d4e --- /dev/null +++ b/tests/gui/books/basic/src/rust-logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/tests/gui/image-zoom.goml b/tests/gui/image-zoom.goml new file mode 100644 index 0000000000..79df878ca8 --- /dev/null +++ b/tests/gui/image-zoom.goml @@ -0,0 +1,46 @@ +// This test ensures that the image zoom-in/zoom-out works as expected. + +define-function: ( + "check-image-not-zoomed-in", + [], + block { + // The "zoomed in" image should not be displayed. + assert-css: (".checkbox-label > .img-wrapper", {"display": "none"}) + // The cursor for the image should be "zoom-in". + assert-css: (".checkbox-label > img", {"cursor": "zoom-in"}) + }, +) + +define-function: ( + "check-image-zoomed-in", + [], + block { + // The image wrapper should now be displayed and have a "zoom-out" cursor. + assert-css: (".checkbox-label > .img-wrapper", {"display": "flex", "cursor": "zoom-out"}) + // Same for the image it contains. + assert-css: ( + ".checkbox-label > .img-wrapper img", + {"display": "block", "cursor": "zoom-out"}, + ) + }, +) + +go-to: |DOC_PATH| + "basic/index.html" +call-function: ("check-image-not-zoomed-in", {}) + +// We click on the image to "zoom in". +click: ".checkbox-label > img" +call-function: ("check-image-zoomed-in", {}) + +// We click on the wrapper to "zoom out". +click: ".checkbox-label > img" +// We check that everything is back to the previous state. +call-function: ("check-image-not-zoomed-in", {}) + +// We test that the "escape" key also hides the "zoomed in" image. +// We click on the image to "zoom in". +click: ".checkbox-label > img" +call-function: ("check-image-zoomed-in", {}) +press-key: "Escape" +// We check that everything is back to the previous state. +call-function: ("check-image-not-zoomed-in", {}) diff --git a/tests/testsuite/markdown/basic_markdown/expected/images.html b/tests/testsuite/markdown/basic_markdown/expected/images.html index 4dfdf1657a..94820cb9fb 100644 --- a/tests/testsuite/markdown/basic_markdown/expected/images.html +++ b/tests/testsuite/markdown/basic_markdown/expected/images.html @@ -1,3 +1,3 @@

Images

-

Image “alt” & " "text" & <stuff> url <em>html</em> — hard break

-

Image with title

\ No newline at end of file +

+

\ No newline at end of file diff --git a/tests/testsuite/print/relative_links/expected/print.html b/tests/testsuite/print/relative_links/expected/print.html index bff4ce1c65..00dfcf8374 100644 --- a/tests/testsuite/print/relative_links/expected/print.html +++ b/tests/testsuite/print/relative_links/expected/print.html @@ -13,7 +13,7 @@

inside but doesn’t exist with anchor. Link inside to html. Link inside to html with anchor.

-

Some image

+

HTML Link

raw html

Some section