From 48a944a0f23053dc6c4b498a812130ad831ee195 Mon Sep 17 00:00:00 2001 From: Michael Howell Date: Thu, 8 Feb 2024 15:00:29 -0700 Subject: [PATCH] rustdoc: add three-column layout for large desktops This commit adds a floating TOC box to the right, leaving the sibling/module/crate navigation on the left. This kicks in at a size a little below 1920x1080, where desktops with very wide monitors are: it's also around the point where the content area can be full width while allowing two sidebars. It only kicks in if the browser supports grid layouts, but that should be most of them, and we can't get rid of the two-column layout anyway, since it's the layout you get on something like a portrait iPad. This design, where it can be used, is meant to clearly split up the table of contents and the site navigation, so the right side floating box has the same color as the page while the left sidebar does not. It also pushes it down further, so that it's not as high as the search bar, though that's a bit more subtle than the color. --- src/librustdoc/html/static/css/rustdoc.css | 142 ++++++++++++++++-- src/librustdoc/html/static/js/main.js | 30 +++- src/librustdoc/html/templates/page.html | 32 ++-- src/librustdoc/html/templates/sidebar.html | 25 +-- .../rustdoc-gui/sidebar-modnav-position.goml | 31 +++- tests/rustdoc-gui/sidebar.goml | 24 +-- tests/rustdoc/sidebar/module.rs | 8 +- tests/rustdoc/sidebar/top-toc-html.rs | 14 +- tests/rustdoc/sidebar/top-toc-idmap.rs | 12 +- 9 files changed, 244 insertions(+), 74 deletions(-) diff --git a/src/librustdoc/html/static/css/rustdoc.css b/src/librustdoc/html/static/css/rustdoc.css index 38154dee3e287..602a80c69b46f 100644 --- a/src/librustdoc/html/static/css/rustdoc.css +++ b/src/librustdoc/html/static/css/rustdoc.css @@ -34,6 +34,13 @@ xmlns="http://www.w3.org/2000/svg" fill="black" height="18px">\ '); --button-left-margin: 4px; --button-border-radius: 2px; + /* Used to manage the big screen three column layout */ + --width-limiter-width: 960px; + --desktop-grid-column-gap: 45px; + --container-border-radius: 6px; + /* height of header, plus header margin, plus top logo section */ + --desktop-grid-toc-top: calc((1.25 * 1.5rem) + 76px + 23px); + --desktop-grid-toc-max-width: 512px; } /* See FiraSans-LICENSE.txt for the Fira Sans license. */ @@ -336,7 +343,7 @@ button#toggle-all-docs { main { position: relative; flex-grow: 1; - padding: 10px 15px 40px 45px; + padding: 10px 15px 40px var(--desktop-grid-column-gap); min-width: 0; /* avoid growing beyond the size limit */ } @@ -345,7 +352,7 @@ main { } .width-limiter { - max-width: 960px; + max-width: var(--width-limiter-width); margin-right: auto; } @@ -369,6 +376,7 @@ pre { } pre.item-decl { overflow-x: auto; + border-radius: var(--container-border-radius); } /* This rule allows to have scrolling on the X axis. */ .item-decl .type-contents-toggle { @@ -460,7 +468,7 @@ img { .sidebar-resizing .sidebar { position: fixed; } -.sidebar-resizing > body { +.sidebar-resizing .rustdoc { padding-left: var(--resizing-sidebar-width); } @@ -534,7 +542,7 @@ img { scrollbar-width: initial; scrollbar-color: var(--scrollbar-color); } -.sidebar { +.sidebar, #rustdoc-toc section, #rustdoc-modnav section { scrollbar-width: thin; scrollbar-color: var(--scrollbar-color); } @@ -543,17 +551,24 @@ img { ::-webkit-scrollbar { width: 12px; } -.sidebar::-webkit-scrollbar { +.sidebar::-webkit-scrollbar, +#rustdoc-toc section::-webkit-scrollbar, +#rustdoc-modnav section::-webkit-scrollbar { width: 8px; } ::-webkit-scrollbar-track { -webkit-box-shadow: inset 0; background-color: var(--scrollbar-track-background-color); } -.sidebar::-webkit-scrollbar-track { +.sidebar::-webkit-scrollbar-track, +#rustdoc-toc section::-webkit-scrollbar-track, +#rustdoc-modnav section::-webkit-scrollbar-track { background-color: var(--scrollbar-track-background-color); } -::-webkit-scrollbar-thumb, .sidebar::-webkit-scrollbar-thumb { +::-webkit-scrollbar-thumb, +.sidebar::-webkit-scrollbar-thumb, +#rustdoc-toc section::-webkit-scrollbar-thumb, +#rustdoc-modnav section::-webkit-scrollbar-thumb { background-color: var(--scrollbar-thumb-background-color); } @@ -742,7 +757,7 @@ ul.block, .block li, .block ul { overflow-wrap: break-word; } -.sidebar-crate + .version { +.sidebar > .version { margin-top: -1rem; margin-bottom: 1rem; } @@ -760,7 +775,7 @@ ul.block, .block li, .block ul { .rustdoc .example-wrap > pre, .rustdoc .scraped-example .src-line-numbers, .rustdoc .scraped-example .src-line-numbers > pre { - border-radius: 6px; + border-radius: var(--container-border-radius); } /* @@ -2078,6 +2093,115 @@ However, it's not needed with smaller screen width because the doc/code block is /* Media Queries */ +/* Very-large-screen mode. */ +@supports (display: grid) and (display: contents) { + @media (min-width: 1600px) { + .rustdoc:not(.src) { + display: grid; + grid-template-columns: + var(--desktop-sidebar-width) + var(--width-limiter-width) + minmax(0, 1fr); + grid-template-rows: min-content 1fr; + grid-template-areas: + "sidebar-title main sidebar-cratenav" + "sidebar-modnav main sidebar-toc"; + grid-column-gap: var(--desktop-grid-column-gap); + } + .sidebar-resizing .rustdoc:not(.src) { + padding-left: 0; + } + .hide-sidebar .rustdoc:not(.src) { + grid-template-columns: + var(--width-limiter-width) + minmax(0, 1fr); + grid-template-rows: minmax(min-content, calc(64px + 0.75rem)) 1fr; + grid-template-areas: + "main sidebar-cratenav" + "main sidebar-toc"; + padding-left: var(--desktop-grid-column-gap); + } + .rustdoc:not(.src) .sidebar, + .rustdoc:not(.src) main { + display: contents; + } + .width-limiter { + grid-area: main; + width: var(--width-limiter-width); + --desktop-sidebar-width: 0; + } + .rustdoc:not(.src) nav.sub { + padding-top: 10px; + } + .rustdoc:not(.src) .doc-sidebar-title { + grid-area: sidebar-title; + background: var(--sidebar-background-color); + position: sticky; + top: 0; + } + .rustdoc:not(.src) .sidebar-crate { + margin-bottom: 0.5rem; + } + .rustdoc:not(.src) #rustdoc-toc, + .rustdoc:not(.src) #rustdoc-cratenav { + grid-area: sidebar-toc; + background: var(--main-background-color); + padding-left: 0; + } + .rustdoc:not(.src) #rustdoc-cratenav { + grid-area: sidebar-cratenav; + align-self: middle; + } + .rustdoc:not(.src) #rustdoc-modnav { + grid-area: sidebar-modnav; + background: var(--sidebar-background-color); + padding-left: 0; + } + .rustdoc:not(.src) #rustdoc-modnav .in-crate { + display: none; + } + .rustdoc:not(.src) #rustdoc-toc section, + .rustdoc:not(.src) #rustdoc-modnav section { + position: sticky; + top: 0; + bottom: 0; + overflow-y: scroll; + overscroll-behavior: contain; + max-height: 100vh; + padding-left: 24px; + } + .rustdoc:not(.src) #rustdoc-toc .location, + .rustdoc:not(.src) #rustdoc-modnav h2 { + margin-top: 0; + } + .rustdoc:not(.src) #rustdoc-modnav section { + top: calc(64px + 0.75rem); + height: calc(100vh - 64px - 0.75rem); + background: var(--sidebar-background-color); + } + .rustdoc:not(.src) #rustdoc-modnav section.scrolled { + border-top: solid 1px var(--border-color); + } + .rustdoc:not(.src) #rustdoc-toc section { + max-height: calc(100vh - (2 * var(--desktop-grid-toc-top))); + top: var(--desktop-grid-toc-top); + margin: 0 var(--desktop-grid-column-gap) var(--desktop-grid-column-gap) 0; + border: solid 1px var(--border-color); + padding: 14px 0 14px var(--sidebar-elems-left-padding); + border-radius: var(--container-border-radius); + max-width: var(--desktop-grid-toc-max-width); + } + .rustdoc:not(.src) #rustdoc-cratenav .block:last-child, + .rustdoc:not(.src) #rustdoc-toc .block:last-child { + margin-bottom: 0; + } + .rustdoc:not(.src) #rustdoc-cratenav a:hover, + .rustdoc:not(.src) #rustdoc-toc a:hover { + background-color: var(--sidebar-background-color); + } + } +} + /* Make sure all the buttons line wrap at the same time */ @media (max-width: 850px) { #search-tabs .count { diff --git a/src/librustdoc/html/static/js/main.js b/src/librustdoc/html/static/js/main.js index 858753a1917d8..40ca61ff90782 100644 --- a/src/librustdoc/html/static/js/main.js +++ b/src/librustdoc/html/static/js/main.js @@ -499,7 +499,7 @@ function preLoadCss(cssUrl) { if (!window.SIDEBAR_ITEMS) { return; } - const sidebar = document.getElementById("rustdoc-modnav"); + const sidebar = document.querySelector("#rustdoc-modnav section"); /** * Append to the sidebar a "block" of links - a heading along with a list (` {# #} + +{% endif %} +{% if self.should_render_blocks() %} + diff --git a/tests/rustdoc-gui/sidebar-modnav-position.goml b/tests/rustdoc-gui/sidebar-modnav-position.goml index eb86d118ab22d..47a8da5c8a2af 100644 --- a/tests/rustdoc-gui/sidebar-modnav-position.goml +++ b/tests/rustdoc-gui/sidebar-modnav-position.goml @@ -9,7 +9,9 @@ // because the sibling module nav is in exactly the same place every time, // it's very easy to find and switch between pages that way. +// First, in tablet-ish mode go-to: "file://" + |DOC_PATH| + "/test_docs/enum.WhoLetTheDogOut.html" +set-window-size: (1280, 800) show-text: true set-local-storage: {"rustdoc-hide-toc": "true"} @@ -19,9 +21,9 @@ define-function: ( block { go-to: "file://" + |DOC_PATH| + |url| // Checking results colors. - assert-position: ("#rustdoc-modnav > h2", {"x": |h2_x|, "y": |h2_y|}) + assert-position: ("#rustdoc-modnav > section > h2", {"x": |h2_x|, "y": |h2_y|}) assert-position: ( - "#rustdoc-modnav > ul:first-of-type > li:first-of-type", + "#rustdoc-modnav > section > ul:first-of-type > li:first-of-type", {"x": |x|, "y": |y|} ) }, @@ -29,16 +31,33 @@ define-function: ( // First, at test_docs root go-to: "file://" + |DOC_PATH| + "/test_docs/enum.WhoLetTheDogOut.html" -store-position: ("#rustdoc-modnav > h2", {"x": h2_x, "y": h2_y}) -store-position: ("#rustdoc-modnav > ul:first-of-type > li:first-of-type", {"x": x, "y": y}) +store-position: ("#rustdoc-modnav > section > h2", {"x": h2_x, "y": h2_y}) +store-position: ("#rustdoc-modnav > section > ul:first-of-type > li:first-of-type", {"x": x, "y": y}) call-function: ("check-positions", {"url": "/test_docs/enum.WhoLetTheDogOut.html"}) call-function: ("check-positions", {"url": "/test_docs/struct.StructWithPublicUndocumentedFields.html"}) call-function: ("check-positions", {"url": "/test_docs/codeblock_sub/index.html"}) // Now in a submodule go-to: "file://" + |DOC_PATH| + "/test_docs/fields/struct.Struct.html" -store-position: ("#rustdoc-modnav > h2", {"x": h2_x, "y": h2_y}) -store-position: ("#rustdoc-modnav > ul:first-of-type > li:first-of-type", {"x": x, "y": y}) +store-position: ("#rustdoc-modnav > section > h2", {"x": h2_x, "y": h2_y}) +store-position: ("#rustdoc-modnav > section > ul:first-of-type > li:first-of-type", {"x": x, "y": y}) call-function: ("check-positions", {"url": "/test_docs/fields/struct.Struct.html"}) call-function: ("check-positions", {"url": "/test_docs/fields/union.Union.html"}) call-function: ("check-positions", {"url": "/test_docs/fields/enum.Enum.html"}) + +// Now try similar, but in desktop-ish mode +// Unlike in tablet mode, desktop mode pulls the TOC out of the main left sidebar into its own pane, +// so modnav (which remains in the left sidebar) doesn't move regardless. +// It also has the same position as the item-decl. +set-local-storage: {"rustdoc-hide-toc": "false"} +set-window-size: (1920, 1080) +go-to: "file://" + |DOC_PATH| + "/test_docs/enum.WhoLetTheDogOut.html" +store-position: ("#rustdoc-modnav > section > h2", {"x": h2_x, "y": h2_y}) +store-position: ("#rustdoc-modnav > section > ul:first-of-type > li:first-of-type", {"x": x, "y": y}) +call-function: ("check-positions", {"url": "/test_docs/enum.WhoLetTheDogOut.html"}) +compare-elements-position: ("#rustdoc-toc > section", ".item-decl", ["y"]) +compare-elements-css: ("#rustdoc-toc > section", ".item-decl", ["border-radius"]) +call-function: ("check-positions", {"url": "/test_docs/struct.StructWithPublicUndocumentedFields.html"}) +compare-elements-position: ("#rustdoc-toc > section", ".item-decl", ["y"]) +compare-elements-css: ("#rustdoc-toc > section", ".item-decl", ["border-radius"]) +call-function: ("check-positions", {"url": "/test_docs/codeblock_sub/index.html"}) diff --git a/tests/rustdoc-gui/sidebar.goml b/tests/rustdoc-gui/sidebar.goml index 7794cdbe9e264..2c42e84fed6b1 100644 --- a/tests/rustdoc-gui/sidebar.goml +++ b/tests/rustdoc-gui/sidebar.goml @@ -45,7 +45,7 @@ call-function: ( call-function: ("switch-theme", {"theme": "light"}) -assert-text: (".sidebar > .sidebar-crate > h2 > a", "test_docs") +assert-text: (".sidebar-crate > h2 > a", "test_docs") // Crate root has no "location" element assert-count: (".sidebar .location", 0) assert-count: (".sidebar h2", 1) @@ -69,7 +69,7 @@ assert-text: ("#structs + .item-table .item-name > a", "Foo") click: "#structs + .item-table .item-name > a" // PAGE: struct.Foo.html -assert-count: (".sidebar .sidebar-crate", 1) +assert-count: (".sidebar-crate", 1) assert-count: (".sidebar .location", 1) assert-count: (".sidebar h2", 3) assert-text: (".sidebar-elems ul.block > li.current > a", "Foo") @@ -91,7 +91,7 @@ click: ".sidebar-elems ul.crate > li:first-child > a" // PAGE: lib2/index.html go-to: "file://" + |DOC_PATH| + "/lib2/index.html" assert-property: (".sidebar", {"clientWidth": "200"}) -assert-text: (".sidebar > .sidebar-crate > h2 > a", "lib2") +assert-text: (".sidebar-crate > h2 > a", "lib2") assert-count: (".sidebar .location", 0) // We check that we have the crates list and that the "current" on is now "lib2". assert-text: (".sidebar-elems ul.crate > li.current > a", "lib2") @@ -108,7 +108,7 @@ click: "#functions + .item-table .item-name > a" // In items containing no items (like functions or constants) and in modules, we have no // "location" elements. Only the crate and optional parent module. // This page, being directly below the crate, only has its heading. -assert-text: (".sidebar > .sidebar-crate > h2 > a", "lib2") +assert-text: (".sidebar-crate > h2 > a", "lib2") assert-count: (".sidebar .location", 0) assert-count: (".sidebar h2", 1) assert-text: (".sidebar-elems ul.block > li.current > a", "foobar") @@ -117,7 +117,7 @@ assert-false: ".sidebar-elems > .crate" go-to: "./module/index.html" assert-property: (".sidebar", {"clientWidth": "200"}) -assert-text: (".sidebar > .sidebar-crate > h2 > a", "lib2") +assert-text: (".sidebar-crate > h2 > a", "lib2") assert-text: (".sidebar .location", "Module module") assert-count: (".sidebar .location", 1) assert-text: (".sidebar-elems ul.block > li.current > a", "module") @@ -126,8 +126,8 @@ assert-text: (".sidebar-elems ul.block > li.current > a", "module") // - Module name, followed by TOC for module headings // - "In crate [name]" parent pointer, followed by sibling navigation assert-count: (".sidebar h2", 3) -assert-text: (".sidebar > .sidebar-elems > #rustdoc-modnav > h2", "In crate lib2") -assert-property: (".sidebar > .sidebar-elems > #rustdoc-modnav > h2 > a", { +assert-text: ("#rustdoc-modnav > section > h2", "In crate lib2") +assert-property: ("#rustdoc-modnav > section > h2 > a", { "href": "/lib2/index.html", }, ENDS_WITH) // We check that we don't have the crate list. @@ -135,10 +135,10 @@ assert-false: ".sidebar-elems > .crate" go-to: "./sub_module/sub_sub_module/index.html" assert-property: (".sidebar", {"clientWidth": "200"}) -assert-text: (".sidebar > .sidebar-crate > h2 > a", "lib2") +assert-text: (".sidebar-crate > h2 > a", "lib2") assert-text: (".sidebar .location", "Module sub_sub_module") -assert-text: (".sidebar > .sidebar-elems > #rustdoc-modnav > h2", "In lib2::module::sub_module") -assert-property: (".sidebar > .sidebar-elems > #rustdoc-modnav > h2 > a", { +assert-text: ("#rustdoc-modnav > section > h2", "In lib2::module::sub_module") +assert-property: ("#rustdoc-modnav > section > h2 > a", { "href": "/module/sub_module/index.html", }, ENDS_WITH) assert-text: (".sidebar-elems ul.block > li.current > a", "sub_sub_module") @@ -170,14 +170,14 @@ assert-property: (".sidebar", {"clientWidth": "200"}) // Checks that all.html and index.html have their sidebar link in the same place. go-to: "file://" + |DOC_PATH| + "/test_docs/index.html" -store-property: (".sidebar .sidebar-crate h2 a", { +store-property: (".sidebar-crate h2 a", { "clientWidth": index_sidebar_width, "clientHeight": index_sidebar_height, "offsetTop": index_sidebar_y, "offsetLeft": index_sidebar_x, }) go-to: "file://" + |DOC_PATH| + "/test_docs/all.html" -assert-property: (".sidebar .sidebar-crate h2 a", { +assert-property: (".sidebar-crate h2 a", { "clientWidth": |index_sidebar_width|, "clientHeight": |index_sidebar_height|, "offsetTop": |index_sidebar_y|, diff --git a/tests/rustdoc/sidebar/module.rs b/tests/rustdoc/sidebar/module.rs index b5bcb9f232c75..c0a0df9616ea3 100644 --- a/tests/rustdoc/sidebar/module.rs +++ b/tests/rustdoc/sidebar/module.rs @@ -1,16 +1,16 @@ #![crate_name = "foo"] //@ has 'foo/index.html' -//@ has - '//section[@id="rustdoc-toc"]/h3' 'Crate Items' +//@ has - '//div[@id="rustdoc-toc"]/section/h3' 'Crate Items' //@ has 'foo/bar/index.html' -//@ has - '//section[@id="rustdoc-toc"]/h3' 'Module Items' +//@ has - '//div[@id="rustdoc-toc"]/section/h3' 'Module Items' pub mod bar { //@ has 'foo/bar/struct.Baz.html' - //@ !has - '//section[@id="rustdoc-toc"]/h3' 'Module Items' + //@ !has - '//div[@id="rustdoc-toc"]/section/h3' 'Module Items' pub struct Baz; } //@ has 'foo/baz/index.html' -//@ !has - '//section[@id="rustdoc-toc"]/h3' 'Module Items' +//@ !has - '//div[@id="rustdoc-toc"]/section/h3' 'Module Items' pub mod baz {} diff --git a/tests/rustdoc/sidebar/top-toc-html.rs b/tests/rustdoc/sidebar/top-toc-html.rs index 0f60396043475..e63f56c149973 100644 --- a/tests/rustdoc/sidebar/top-toc-html.rs +++ b/tests/rustdoc/sidebar/top-toc-html.rs @@ -14,10 +14,10 @@ //@ has foo/index.html // User header -//@ has - '//section[@id="rustdoc-toc"]/h3' 'Sections' -//@ has - '//section[@id="rustdoc-toc"]/ul[@class="block top-toc"]/li/a[@href="#basic-link-emphasis-very-emphasis-and-code"]/@title' 'Basic link, emphasis, very emphasis and `code`' -//@ has - '//section[@id="rustdoc-toc"]/ul[@class="block top-toc"]/li/a[@href="#basic-link-emphasis-very-emphasis-and-code"]' 'Basic link, emphasis, very emphasis and code' -//@ count - '//section[@id="rustdoc-toc"]/ul[@class="block top-toc"]/li/a[@href="#basic-link-emphasis-very-emphasis-and-code"]/em' 0 -//@ count - '//section[@id="rustdoc-toc"]/ul[@class="block top-toc"]/li/a[@href="#basic-link-emphasis-very-emphasis-and-code"]/a' 0 -//@ count - '//section[@id="rustdoc-toc"]/ul[@class="block top-toc"]/li/a[@href="#basic-link-emphasis-very-emphasis-and-code"]/code' 1 -//@ has - '//section[@id="rustdoc-toc"]/ul[@class="block top-toc"]/li/a[@href="#basic-link-emphasis-very-emphasis-and-code"]/code' 'code' +//@ has - '//div[@id="rustdoc-toc"]//h3' 'Sections' +//@ has - '//div[@id="rustdoc-toc"]//ul[@class="block top-toc"]/li/a[@href="#basic-link-emphasis-very-emphasis-and-code"]/@title' 'Basic link, emphasis, very emphasis and `code`' +//@ has - '//div[@id="rustdoc-toc"]//ul[@class="block top-toc"]/li/a[@href="#basic-link-emphasis-very-emphasis-and-code"]' 'Basic link, emphasis, very emphasis and code' +//@ count - '//div[@id="rustdoc-toc"]//ul[@class="block top-toc"]/li/a[@href="#basic-link-emphasis-very-emphasis-and-code"]/em' 0 +//@ count - '//div[@id="rustdoc-toc"]//ul[@class="block top-toc"]/li/a[@href="#basic-link-emphasis-very-emphasis-and-code"]/a' 0 +//@ count - '//div[@id="rustdoc-toc"]//ul[@class="block top-toc"]/li/a[@href="#basic-link-emphasis-very-emphasis-and-code"]/code' 1 +//@ has - '//div[@id="rustdoc-toc"]//ul[@class="block top-toc"]/li/a[@href="#basic-link-emphasis-very-emphasis-and-code"]/code' 'code' diff --git a/tests/rustdoc/sidebar/top-toc-idmap.rs b/tests/rustdoc/sidebar/top-toc-idmap.rs index af07cb4179b1f..a1b475ecf5b33 100644 --- a/tests/rustdoc/sidebar/top-toc-idmap.rs +++ b/tests/rustdoc/sidebar/top-toc-idmap.rs @@ -16,11 +16,11 @@ //@ has foo/index.html // User header -//@ has - '//section[@id="rustdoc-toc"]/ul[@class="block top-toc"]/li/a[@href="#structs"]' 'Structs' +//@ has - '//div[@id="rustdoc-toc"]//ul[@class="block top-toc"]/li/a[@href="#structs"]' 'Structs' //@ has - '//details[@class="toggle top-doc"]/div[@class="docblock"]/h2[@id="structs"]' 'Structs' // Built-in header -//@ has - '//section[@id="rustdoc-toc"]/ul[@class="block"]/li/a[@href="#structs-1"]' 'Structs' -//@ has - '//section[@id="main-content"]/h2[@id="structs-1"]' 'Structs' +//@ has - '//div[@id="rustdoc-toc"]//ul[@class="block"]/li/a[@href="#structs-1"]' 'Structs' +//@ has - '//section[@id="main-content"]//h2[@id="structs-1"]' 'Structs' /// # Fields /// ## Fields @@ -31,12 +31,12 @@ //@ has foo/struct.MyStruct.html // User header -//@ has - '//section[@id="rustdoc-toc"]/ul[@class="block top-toc"]/li/a[@href="#fields-1"]' 'Fields' +//@ has - '//div[@id="rustdoc-toc"]//ul[@class="block top-toc"]/li/a[@href="#fields-1"]' 'Fields' //@ has - '//details[@class="toggle top-doc"]/div[@class="docblock"]/h2[@id="fields-1"]' 'Fields' // Only one level of nesting -//@ count - '//section[@id="rustdoc-toc"]/ul[@class="block top-toc"]//a' 2 +//@ count - '//div[@id="rustdoc-toc"]//ul[@class="block top-toc"]//a' 2 // Built-in header -//@ has - '//section[@id="rustdoc-toc"]/h3/a[@href="#fields"]' 'Fields' +//@ has - '//div[@id="rustdoc-toc"]//h3/a[@href="#fields"]' 'Fields' //@ has - '//section[@id="main-content"]/h2[@id="fields"]' 'Fields' pub struct MyStruct {