Merge pull request #2822 from ehuss/dynamic-toc

Add sidebar heading navigation
This commit is contained in:
Eric Huss 2025-08-27 22:24:50 +00:00 committed by GitHub
commit 06af133838
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
24 changed files with 732 additions and 6 deletions

View file

@ -485,6 +485,9 @@ pub struct HtmlConfig {
///
/// The default is `true`.
pub hash_files: bool,
/// If enabled, the sidebar includes navigation for headers on the current
/// page. Default is `true`.
pub sidebar_header_nav: bool,
}
impl Default for HtmlConfig {
@ -512,6 +515,7 @@ impl Default for HtmlConfig {
live_reload_endpoint: None,
redirect: HashMap::new(),
hash_files: true,
sidebar_header_nav: true,
}
}
}

View file

@ -730,3 +730,21 @@ html:not(.sidebar-resizing) .sidebar {
/* mdbook's margin for h2 is way too large. */
margin: 10px;
}
.current-header {
/* Allows the circle positioning. */
position: relative
}
/* Places a circle just before the current header in the sidebar. */
.current-header::before {
content: '';
position: absolute;
left: -16px;
top: 0;
margin-top: 10px;
width: 8px;
height: 8px;
background-color: var(--sidebar-active);
border-radius: 50%;
}

View file

@ -72,3 +72,312 @@ class MDBookSidebarScrollbox extends HTMLElement {
}
}
window.customElements.define('mdbook-sidebar-scrollbox', MDBookSidebarScrollbox);
{{#if sidebar_header_nav}}
// ---------------------------------------------------------------------------
// Support for dynamically adding headers to the sidebar.
// This is a debugging tool for the threshold which you can enable in the console.
// eslint-disable-next-line prefer-const
let mdbookThresholdDebug = false;
(function() {
// This is used to detect which direction the page has scrolled since the
// last scroll event.
let lastKnownScrollPosition = 0;
// This is the threshold in px from the top of the screen where it will
// consider a header the "current" header when scrolling down.
const defaultDownThreshold = 150;
// Same as defaultDownThreshold, except when scrolling up.
const defaultUpThreshold = 300;
// The threshold is a virtual horizontal line on the screen where it
// considers the "current" header to be above the line. The threshold is
// modified dynamically to handle headers that are near the bottom of the
// screen, and to slightly offset the behavior when scrolling up vs down.
let threshold = defaultDownThreshold;
// This is used to disable updates while scrolling. This is needed when
// clicking the header in the sidebar, which triggers a scroll event. It
// is somewhat finicky to detect when the scroll has finished, so this
// uses a relatively dumb system of disabling scroll updates for a short
// time after the click.
let disableScroll = false;
// Array of header elements on the page.
let headers;
// Array of li elements that are initially collapsed headers in the sidebar.
// I'm not sure why eslint seems to have a false positive here.
// eslint-disable-next-line prefer-const
let headerToggles = [];
function drawDebugLine() {
if (!document.body) {
return;
}
const id = 'mdbook-threshold-debug-line';
const existingLine = document.getElementById(id);
if (existingLine) {
existingLine.remove();
}
const line = document.createElement('div');
line.id = id;
line.style.cssText = `
position: fixed;
top: ${threshold}px;
left: 0;
width: 100vw;
height: 2px;
background-color: red;
z-index: 9999;
pointer-events: none;
`;
document.body.appendChild(line);
}
// Updates the threshold based on the scroll position.
function updateThreshold() {
const scrollTop = window.pageYOffset || document.documentElement.scrollTop;
const windowHeight = window.innerHeight;
const documentHeight = document.documentElement.scrollHeight;
// The number of pixels below the viewport, at most documentHeight.
// This is used to push the threshold down to the bottom of the page
// as the user scrolls towards the bottom.
const pixelsBelow = Math.max(0, documentHeight - (scrollTop + windowHeight));
// The number of pixels above the viewport, at most defaultDownThreshold.
// Similar to pixelsBelow, this is used to push the threshold back towards
// the top when reaching the top of the page.
const pixelsAbove = Math.max(0, defaultDownThreshold - scrollTop);
// How much the threshold should be offset once it gets close to the
// bottom of the page.
let bottomAdd = Math.max(0, windowHeight - pixelsBelow - defaultDownThreshold);
// Adjusts bottomAdd for a small document. The calculation above
// assumes the document is at least twice the windowheight in size. If
// it is less than that, then bottomAdd needs to be shrunk
// proportional to the difference in size.
if (documentHeight < windowHeight * 2) {
const maxPixelsBelow = documentHeight - windowHeight;
const t = 1 - pixelsBelow / maxPixelsBelow;
const clamp = Math.max(0, Math.min(1, t));
bottomAdd *= clamp;
}
let scrollingDown = true;
if (scrollTop < lastKnownScrollPosition) {
scrollingDown = false;
}
if (scrollingDown) {
// When scrolling down, move the threshold up towards the default
// downwards threshold position. If near the bottom of the page,
// bottomAdd will offset the threshold towards the bottom of the
// page.
const amountScrolledDown = scrollTop - lastKnownScrollPosition;
const adjustedDefault = defaultDownThreshold + bottomAdd;
threshold = Math.max(adjustedDefault, threshold - amountScrolledDown);
} else {
// When scrolling up, move the threshold down towards the default
// upwards threshold position. If near the bottom of the page,
// quickly transition the threshold back up where it normally
// belongs.
const amountScrolledUp = lastKnownScrollPosition - scrollTop;
const adjustedDefault = defaultUpThreshold - pixelsAbove
+ Math.max(0, bottomAdd - defaultDownThreshold);
threshold = Math.min(adjustedDefault, threshold + amountScrolledUp);
}
lastKnownScrollPosition = scrollTop;
}
// Updates which headers in the sidebar should be expanded. If the current
// header is inside a collapsed group, then it, and all its parents should
// be expanded.
function updateHeaderExpanded(currentA) {
// Add expanded to all header-item li ancestors.
let current = currentA.parentElement.parentElement.parentElement;
while (current.tagName === 'LI') {
const prevSibling = current.previousElementSibling;
if (prevSibling !== null
&& prevSibling.tagName === 'LI'
&& prevSibling.classList.contains('header-item')) {
prevSibling.classList.add('expanded');
current = prevSibling.parentElement.parentElement;
} else {
break;
}
}
}
// Updates which header is marked as the "current" header in the sidebar.
// This is done with a virtual Y threshold, where headers at or below
// that line will be considered the current one.
function updateCurrentHeader() {
if (mdbookThresholdDebug) {
drawDebugLine();
}
if (!headers || !headers.length) {
return;
}
// Reset the classes, which will be rebuilt below.
const els = document.getElementsByClassName('current-header');
for (const el of els) {
el.classList.remove('current-header');
}
for (const toggle of headerToggles) {
toggle.classList.remove('expanded');
}
// Find the last header that is above the threshold.
let lastHeader = null;
for (const header of headers) {
const rect = header.getBoundingClientRect();
if (rect.top <= threshold) {
lastHeader = header;
} else {
break;
}
}
if (lastHeader === null) {
lastHeader = headers[0];
const rect = lastHeader.getBoundingClientRect();
const windowHeight = window.innerHeight;
if (rect.top >= windowHeight) {
return;
}
}
// Get the anchor in the summary.
const href = '#' + lastHeader.id;
const a = [...document.querySelectorAll('.header-in-summary')]
.find(element => element.getAttribute('href') === href);
if (!a) {
return;
}
a.classList.add('current-header');
updateHeaderExpanded(a);
}
// Updates which header is "current" based on the threshold line.
function reloadCurrentHeader() {
if (disableScroll) {
return;
}
updateThreshold();
updateCurrentHeader();
}
// When clicking on a header in the sidebar, this adjusts the threshold so
// that it is located next to the header. This is so that header becomes
// "current".
function headerThresholdClick(event) {
// See disableScroll description why this is done.
disableScroll = true;
setTimeout(() => {
disableScroll = false;
}, 100);
// requestAnimationFrame is used to delay the update of the "current"
// header until after the scroll is done, and the header is in the new
// position.
requestAnimationFrame(() => {
requestAnimationFrame(() => {
// Closest is needed because if it has child elements like <code>.
const a = event.target.closest('a');
const href = a.getAttribute('href');
const targetId = href.substring(1);
const targetElement = document.getElementById(targetId);
if (targetElement) {
threshold = targetElement.getBoundingClientRect().bottom;
updateCurrentHeader();
}
});
});
}
// Scans page for headers and adds them to the sidebar.
document.addEventListener('DOMContentLoaded', function() {
const activeSection = document.querySelector('#mdbook-sidebar .active');
if (activeSection === null) {
return;
}
const activeItem = activeSection.parentElement;
const activeList = activeItem.parentElement;
// Build a tree of headers in the sidebar.
const rootLi = document.createElement('li');
rootLi.classList.add('header-item');
rootLi.classList.add('expanded');
const rootOl = document.createElement('ol');
rootOl.classList.add('section');
rootLi.appendChild(rootOl);
const stack = [{ level: 0, ol: rootOl }];
// The level where it will start folding deeply nested headers.
const foldLevel = 3;
const main = document.getElementsByTagName('main')[0];
headers = Array.from(main.querySelectorAll('h2, h3, h4, h5, h6'))
.filter(h => h.id !== '' && h.children.length && h.children[0].tagName === 'A');
if (headers.length === 0) {
return;
}
for (let i = 0; i < headers.length; i++) {
const header = headers[i];
const level = parseInt(header.tagName.charAt(1));
const li = document.createElement('li');
li.classList.add('header-item');
li.classList.add('expanded');
if (level < foldLevel) {
li.classList.add('expanded');
}
const a = document.createElement('a');
a.href = '#' + header.id;
a.classList.add('header-in-summary');
a.innerHTML = header.children[0].innerHTML;
a.addEventListener('click', headerThresholdClick);
li.appendChild(a);
const nextHeader = headers[i + 1];
if (nextHeader !== undefined) {
const nextLevel = parseInt(nextHeader.tagName.charAt(1));
if (nextLevel > level && level >= foldLevel) {
const div = document.createElement('div');
div.textContent = '❱';
const toggle = document.createElement('a');
toggle.classList.add('toggle');
toggle.classList.add('header-toggle');
toggle.appendChild(div);
toggle.addEventListener('click', () => {
li.classList.toggle('expanded');
});
li.appendChild(toggle);
headerToggles.push(li);
}
}
// Find the appropriate parent level.
while (stack.length > 1 && stack[stack.length - 1].level >= level) {
stack.pop();
}
const currentParent = stack[stack.length - 1];
currentParent.ol.appendChild(li);
// Create new nested ol for potential children.
const nestedOl = document.createElement('ol');
nestedOl.classList.add('section');
const nestedLi = document.createElement('li');
nestedLi.appendChild(nestedOl);
currentParent.ol.appendChild(nestedLi);
stack.push({ level: level, ol: nestedOl });
}
activeList.insertBefore(rootLi, activeItem.nextSibling);
});
document.addEventListener('DOMContentLoaded', reloadCurrentHeader);
document.addEventListener('scroll', reloadCurrentHeader, { passive: true });
})();
{{/if}}

View file

@ -615,6 +615,10 @@ fn make_data(
data.insert("print_enable".to_owned(), json!(html_config.print.enable));
data.insert("fold_enable".to_owned(), json!(html_config.fold.enable));
data.insert("fold_level".to_owned(), json!(html_config.fold.level));
data.insert(
"sidebar_header_nav".to_owned(),
json!(html_config.sidebar_header_nav),
);
let search = html_config.search.clone();
if cfg!(feature = "search") {

View file

@ -108,6 +108,7 @@ edit-url-template = "https://github.com/rust-lang/mdBook/edit/master/guide/{path
site-url = "/example-book/"
cname = "myproject.rs"
input-404 = "not-found.md"
sidebar-header-nav = true
```
The following configuration options are available:
@ -166,6 +167,7 @@ The following configuration options are available:
Chapter HTML files are not renamed.
Static CSS and JS files can reference each other using `{{ resource "filename" }}` directives.
Defaults to `true`.
- **sidebar-header-nav:** If `true`, the sidebar will contain navigation for headers on the current page. Default is `true`.
[custom domain]: https://docs.github.com/en/github/working-with-github-pages/managing-a-custom-domain-for-your-github-pages-site

View file

@ -1,6 +1,6 @@
{
"dependencies": {
"browser-ui-test": "0.21.1",
"browser-ui-test": "0.21.2",
"eslint": "^9.34.0"
},
"scripts": {

View file

@ -28,6 +28,14 @@
- [Syntax Highlight](languages/highlight.md)
- [Rust Specific](rust/README.md)
- [Rust Codeblocks](rust/rust_codeblock.md)
- [Heading Navigation](headings/README.md)
- [Empty page](headings/empty.md)
- [Large text before first heading](headings/large-intro.md)
- [Normal text before first heading](headings/normal-intro.md)
- [Collapsed headings](headings/collapsed.md)
- [Headings with markup](headings/markup.md)
- [Current scrolls to bottom](headings/current-to-bottom.md)
- [Last numbered chapter](last.md)
---

View file

@ -0,0 +1 @@
# Heading Navigation

View file

@ -0,0 +1,91 @@
# Collapsed headings
Tests collapsed headings.
## Heading 1
1\
2\
3\
4\
5
### Heading 1.1
1\
2\
3\
4\
5
### Heading 1.2
1\
2\
3\
4\
5
#### Heading 1.2.1
1\
2\
3\
4\
5
#### Heading 1.2.2
1\
2\
3\
4\
5
### Heading 1.3
1\
2\
3\
4\
5
## Heading 2
1\
2\
3\
4\
5
### Heading 2.1
1\
2\
3\
4\
5
#### Heading 2.1.1
1\
2\
3\
4\
5
##### Heading 2.1.1.1
1\
2\
3\
4\
5
###### Heading 2.1.1.1.1
1\
2\
3\
4\
5

View file

@ -0,0 +1,46 @@
# Current scrolls to bottom
Checks that the "current" header works even when there are headers near the bottom.
## First header
<span id="scroll-to-1">1</span>\
<span id="scroll-to-2">2</span>\
<span id="scroll-to-3">3</span>\
<span id="scroll-to-4">4</span>\
<span id="scroll-to-5">5</span>\
<span id="scroll-to-6">6</span>\
<span id="scroll-to-7">7</span>\
<span id="scroll-to-8">8</span>\
<span id="scroll-to-9">9</span>\
<span id="scroll-to-10">10</span>\
<span id="scroll-to-11">11</span>\
<span id="scroll-to-12">12</span>\
<span id="scroll-to-13">13</span>\
<span id="scroll-to-14">14</span>\
<span id="scroll-to-15">15</span>\
<span id="scroll-to-16">16</span>\
<span id="scroll-to-17">17</span>\
<span id="scroll-to-18">18</span>\
<span id="scroll-to-19">19</span>\
<span id="scroll-to-20">20</span>
## Second header
<span id="scroll-to-21">21</span>
### Second sub-header
<span id="scroll-to-22">22</span>
## Third header
<span id="scroll-to-23">23</span>
## Fourth header
<span id="scroll-to-24">24</span>
## Fifth header
<span id="scroll-to-25">25</span>

View file

@ -0,0 +1 @@
# Empty page

View file

@ -0,0 +1,47 @@
# Large text before first heading
This tests what happens if there is a lot of text before the first header, which is off the bottom of the screen.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
## First header
Text for first header.

View file

@ -0,0 +1,19 @@
# Headings with markup
Tests that heading markup gets copied to the sidebar.
## Heading with `code` or *italic* or **bold** or ~~strike~~
Basic markup should be copied.
## Heading with a [link](../index.html)
Probably not super-wise to have headings with links, but at least they shouldn't explode.
## Heading with a custom id { #custom-id .custom-class }
Make sure navigation works on a custom id.
## Heading with <span>html</span>
What happens if there is inline HTML?

View file

@ -0,0 +1,15 @@
# Normal text before first heading
This test is to ensure the first heading shows up as "current" on page load.
## The first heading
1
2
3
## The second heading
### And a sub heading

1
test_book/src/last.md Normal file
View file

@ -0,0 +1 @@
# Last numbered chapter

View file

@ -0,0 +1,51 @@
// Tests for collapsed heading sidebar navigation.
set-window-size: (1400, 800)
go-to: |DOC_PATH| + "headings/collapsed.html"
assert-count: (".header-item", 12)
assert-count: (".current-header", 1)
assert-text: (".current-header", "Heading 1")
// Collapsed elements do not have "expanded" class.
assert-attribute: ("li:has(> a[href='#heading-12'])", {"class": "header-item"})
assert-attribute: ("li:has(> a[href='#heading-21'])", {"class": "header-item"})
// Click 1.2, doesn't change expanded.
click: "a.header-in-summary[href='#heading-12']"
assert-attribute: ("li:has(> a[href='#heading-12'])", {"class": "header-item"})
assert-attribute: ("li:has(> a[href='#heading-21'])", {"class": "header-item"})
assert-css: ("//li[preceding-sibling::li[1][a[@href='#heading-12']]]/ol", {"display": "none"})
// Click expand chevron.
// 1.2.1 and 1.2.2 should be visible
click: "a.header-in-summary[href='#heading-12'] ~ a.header-toggle"
assert-attribute: ("li:has(> a[href='#heading-12'])", {"class": "header-item expanded"})
assert-attribute: ("li:has(> a[href='#heading-21'])", {"class": "header-item"})
assert-css: ("//li[preceding-sibling::li[1][a[@href='#heading-12']]]/ol", {"display": "block"})
// Click 1.3
click: "a.header-in-summary[href='#heading-13']"
// Everything should be collapsed
assert-attribute: ("li:has(> a[href='#heading-12'])", {"class": "header-item"})
assert-attribute: ("li:has(> a[href='#heading-21'])", {"class": "header-item"})
assert-css: ("//li[preceding-sibling::li[1][a[@href='#heading-12']]]/ol", {"display": "none"})
assert-css: ("//li[preceding-sibling::li[1][a[@href='#heading-21']]]/ol", {"display": "none"})
assert-attribute: ("li:has(> a[href='#heading-12'])", {"class": "header-item"})
assert-attribute: ("li:has(> a[href='#heading-21'])", {"class": "header-item"})
assert-attribute: ("li:has(> a[href='#heading-211'])", {"class": "header-item"})
assert-attribute: ("li:has(> a[href='#heading-2111'])", {"class": "header-item"})
// Scroll to bottom of page
press-key: 'PageDown'
press-key: 'PageDown'
press-key: 'PageDown'
press-key: 'PageDown'
// 2.1.1.1.1 should be visible, and all the chevrons should be open, and expanded should be on each one
assert-attribute: ("li:has(> a[href='#heading-12'])", {"class": "header-item"})
assert-attribute: ("li:has(> a[href='#heading-21'])", {"class": "header-item expanded"})
assert-attribute: ("li:has(> a[href='#heading-211'])", {"class": "header-item expanded"})
assert-attribute: ("li:has(> a[href='#heading-2111'])", {"class": "header-item expanded"})
assert-css: ("//li[preceding-sibling::li[1][a[@href='#heading-12']]]/ol", {"display": "none"})
assert-css: ("//li[preceding-sibling::li[1][a[@href='#heading-21']]]/ol", {"display": "block"})
assert-css: ("//li[preceding-sibling::li[1][a[@href='#heading-211']]]/ol", {"display": "block"})
assert-css: ("//li[preceding-sibling::li[1][a[@href='#heading-2111']]]/ol", {"display": "block"})

View file

@ -0,0 +1,58 @@
// Checks that the "current" header works even when there are headers near the
// bottom.
set-window-size: (1400, 800)
go-to: |DOC_PATH| + "headings/current-to-bottom.html"
assert-count: (".current-header", 1)
assert-text: (".current-header", "First header")
scroll-to: "#scroll-to-1"
assert-text: (".current-header", "First header")
scroll-to: "#scroll-to-2"
assert-text: (".current-header", "First header")
scroll-to: "#scroll-to-3"
assert-text: (".current-header", "First header")
scroll-to: "#scroll-to-4"
assert-text: (".current-header", "First header")
scroll-to: "#scroll-to-5"
assert-text: (".current-header", "First header")
scroll-to: "#scroll-to-6"
assert-text: (".current-header", "First header")
scroll-to: "#scroll-to-7"
assert-text: (".current-header", "First header")
scroll-to: "#scroll-to-8"
assert-text: (".current-header", "First header")
scroll-to: "#scroll-to-9"
assert-text: (".current-header", "First header")
scroll-to: "#scroll-to-10"
assert-text: (".current-header", "First header")
scroll-to: "#scroll-to-11"
assert-text: (".current-header", "First header")
scroll-to: "#scroll-to-12"
assert-text: (".current-header", "First header")
scroll-to: "#scroll-to-13"
assert-text: (".current-header", "First header")
scroll-to: "#scroll-to-14"
assert-text: (".current-header", "First header")
scroll-to: "#scroll-to-15"
assert-text: (".current-header", "First header")
scroll-to: "#scroll-to-16"
assert-text: (".current-header", "First header")
scroll-to: "#scroll-to-17"
assert-text: (".current-header", "First header")
scroll-to: "#scroll-to-18"
assert-text: (".current-header", "First header")
scroll-to: "#scroll-to-19"
assert-text: (".current-header", "First header")
scroll-to: "#scroll-to-20"
assert-text: (".current-header", "First header")
scroll-to: "#scroll-to-21"
wait-for-text: (".current-header", "Second sub-header")
scroll-to: "#scroll-to-22"
assert-text: (".current-header", "Second sub-header")
scroll-to: "#scroll-to-23"
assert-text: (".current-header", "Second sub-header")
scroll-to: "#scroll-to-24"
assert-text: (".current-header", "Second sub-header")
scroll-to: "#scroll-to-25"
wait-for-text: (".current-header", "Fifth header")

View file

@ -0,0 +1,6 @@
// When there aren't any headings, there shouldn't be any header items in the sidebar.
set-window-size: (1400, 800)
go-to: |DOC_PATH| + "headings/empty.html"
assert-count: (".header-item", 0)
assert-count: (".current-header", 0)

View file

@ -0,0 +1,15 @@
// When there is a large intro, there shouldn't be any "current" headers until
// you scroll down and make it visible on screen.
set-window-size: (1400, 800)
go-to: |DOC_PATH| + "headings/large-intro.html"
assert-count: (".header-item", 2)
assert-count: (".current-header", 0)
scroll-to: "#first-header"
wait-for-count: (".current-header", 1)
assert-text: (".current-header", "First header")
// Scrolling back to the top should set it to 0.
scroll-to: (0, 0)
wait-for-count: (".current-header", 0)

View file

@ -0,0 +1,19 @@
// When a header has various markup, the sidebar should replicate it.
set-window-size: (1400, 800)
go-to: |DOC_PATH| + "headings/markup.html"
assert-count: (".header-item", 5)
assert-count: (".current-header", 1)
assert-text: (".current-header", "Heading with code or italic or bold or strike")
assert-property: (".current-header", {"innerHTML": "Heading with <code>code</code> or <em>italic</em> or <strong>bold</strong> or <del>strike</del>"})
// Clicking the custom one should work and should make it current.
click: "a.header-in-summary[href='#custom-id']"
assert-count: (".current-header", 1)
assert-text: (".current-header", "Heading with a custom id")
// Click the one with HTML, and check it.
click: "a.header-in-summary[href='#heading-with-html']"
assert-count: (".current-header", 1)
assert-text: (".current-header", "Heading with html")

View file

@ -0,0 +1,11 @@
// When there is a normal-sized intro, when the page loads the first heading
// should be "current".
set-window-size: (1400, 800)
go-to: |DOC_PATH| + "headings/normal-intro.html"
assert-count: (".header-item", 4)
assert-count: (".current-header", 1)
assert-text: (".current-header", "The first heading")
click: "a[href='#and-a-sub-heading']"
wait-for-text: (".current-header", "And a sub heading")

View file

@ -24,8 +24,8 @@ press-key: 'ArrowRight'
assert-text: ("title", "Heading - mdBook test book")
// Last numbered page
go-to: "../rust/rust_codeblock.html"
assert-text: ("title", "Rust Codeblocks - mdBook test book")
go-to: "../last.html"
assert-text: ("title", "Last numbered chapter - mdBook test book")
// Go to the suffix chapter
press-key: 'ArrowRight'

View file

@ -41,7 +41,7 @@ assert-css: ("#mdbook-search-wrapper", {"display": "none"})
press-key: 's'
wait-for-css-false: ("#mdbook-search-wrapper", {"display": "none"})
// We ensure the search bar has the focus.
assert: "#mdbook-searchbar:focus"
wait-for: "#mdbook-searchbar:focus"
// Pressing a key will therefore update the search input.
press-key: 't'
assert-text: ("#mdbook-searchbar", "t")

View file

@ -5,8 +5,8 @@
// We disable javascript
javascript: false
go-to: |DOC_PATH| + "index.html"
store-value: (height, 1000)
set-window-size: (1000, |height|)
store-value: (height, 1028)
set-window-size: (1028, |height|)
within-iframe: (".sidebar-iframe-outer", block {
assert-size: (" body", {"height": |height|})