Files
synapse/develop/toc-4dc10a98.js
2026-01-07 18:48:31 +00:00

455 lines
36 KiB
JavaScript

// Populate the sidebar
//
// This is a script, and not included directly in the page, to control the total size of the book.
// The TOC contains an entry for each page, so if each page includes a copy of the TOC,
// the total size of the page becomes O(n**2).
class MDBookSidebarScrollbox extends HTMLElement {
constructor() {
super();
}
connectedCallback() {
this.innerHTML = '<ol class="chapter"><li class="chapter-item expanded "><li class="part-title">Introduction</li></li><li class="chapter-item expanded "><span class="chapter-link-wrapper"><a href="welcome_and_overview.html">Welcome and Overview</a></span></li><li class="chapter-item expanded "><li class="part-title">Setup</li></li><li class="chapter-item expanded "><span class="chapter-link-wrapper"><a href="setup/installation.html">Installation</a></span></li><li class="chapter-item expanded "><span class="chapter-link-wrapper"><a href="setup/security.html">Security</a></span></li><li class="chapter-item expanded "><span class="chapter-link-wrapper"><a href="postgres.html">Using Postgres</a></span></li><li class="chapter-item expanded "><span class="chapter-link-wrapper"><a href="reverse_proxy.html">Configuring a Reverse Proxy</a></span></li><li class="chapter-item expanded "><span class="chapter-link-wrapper"><a href="setup/forward_proxy.html">Configuring a Forward/Outbound Proxy</a></span></li><li class="chapter-item expanded "><span class="chapter-link-wrapper"><a href="turn-howto.html">Configuring a Turn Server</a></span><ol class="section"><li class="chapter-item expanded "><span class="chapter-link-wrapper"><a href="setup/turn/coturn.html">coturn TURN server</a></span></li><li class="chapter-item expanded "><span class="chapter-link-wrapper"><a href="setup/turn/eturnal.html">eturnal TURN server</a></span></li></ol><li class="chapter-item expanded "><span class="chapter-link-wrapper"><a href="delegate.html">Delegation</a></span></li><li class="chapter-item expanded "><li class="part-title">Upgrading</li></li><li class="chapter-item expanded "><span class="chapter-link-wrapper"><a href="upgrade.html">Upgrading between Synapse Versions</a></span></li><li class="chapter-item expanded "><li class="part-title">Usage</li></li><li class="chapter-item expanded "><span class="chapter-link-wrapper"><a href="federate.html">Federation</a></span></li><li class="chapter-item expanded "><span class="chapter-link-wrapper"><a href="usage/configuration/index.html">Configuration</a></span><ol class="section"><li class="chapter-item expanded "><span class="chapter-link-wrapper"><a href="usage/configuration/config_documentation.html">Configuration Manual</a></span></li><li class="chapter-item expanded "><span class="chapter-link-wrapper"><a href="usage/configuration/homeserver_sample_config.html">Homeserver Sample Config File</a></span></li><li class="chapter-item expanded "><span class="chapter-link-wrapper"><a href="usage/configuration/logging_sample_config.html">Logging Sample Config File</a></span></li><li class="chapter-item expanded "><span class="chapter-link-wrapper"><a href="structured_logging.html">Structured Logging</a></span></li><li class="chapter-item expanded "><span class="chapter-link-wrapper"><a href="templates.html">Templates</a></span></li><li class="chapter-item expanded "><span class="chapter-link-wrapper"><a href="usage/configuration/user_authentication/index.html">User Authentication</a></span><ol class="section"><li class="chapter-item expanded "><span class="chapter-link-wrapper"><a href="usage/configuration/user_authentication/single_sign_on/index.html">Single-Sign On</a></span><ol class="section"><li class="chapter-item expanded "><span class="chapter-link-wrapper"><a href="openid.html">OpenID Connect</a></span></li><li class="chapter-item expanded "><span class="chapter-link-wrapper"><a href="usage/configuration/user_authentication/single_sign_on/saml.html">SAML</a></span></li><li class="chapter-item expanded "><span class="chapter-link-wrapper"><a href="usage/configuration/user_authentication/single_sign_on/cas.html">CAS</a></span></li><li class="chapter-item expanded "><span class="chapter-link-wrapper"><a href="sso_mapping_providers.html">SSO Mapping Providers</a></span></li></ol><li class="chapter-item expanded "><span class="chapter-link-wrapper"><a href="password_auth_providers.html">Password Auth Providers</a></span></li><li class="chapter-item expanded "><span class="chapter-link-wrapper"><a href="jwt.html">JSON Web Tokens</a></span></li><li class="chapter-item expanded "><span class="chapter-link-wrapper"><a href="usage/configuration/user_authentication/refresh_tokens.html">Refresh Tokens</a></span></li></ol><li class="chapter-item expanded "><span class="chapter-link-wrapper"><a href="CAPTCHA_SETUP.html">Registration Captcha</a></span></li><li class="chapter-item expanded "><span class="chapter-link-wrapper"><a href="application_services.html">Application Services</a></span></li><li class="chapter-item expanded "><span class="chapter-link-wrapper"><a href="server_notices.html">Server Notices</a></span></li><li class="chapter-item expanded "><span class="chapter-link-wrapper"><a href="consent_tracking.html">Consent Tracking</a></span></li><li class="chapter-item expanded "><span class="chapter-link-wrapper"><a href="user_directory.html">User Directory</a></span></li><li class="chapter-item expanded "><span class="chapter-link-wrapper"><a href="message_retention_policies.html">Message Retention Policies</a></span></li><li class="chapter-item expanded "><span class="chapter-link-wrapper"><a href="modules/index.html">Pluggable Modules</a></span><ol class="section"><li class="chapter-item expanded "><span class="chapter-link-wrapper"><a href="modules/writing_a_module.html">Writing a module</a></span><ol class="section"><li class="chapter-item expanded "><span class="chapter-link-wrapper"><a href="modules/spam_checker_callbacks.html">Spam checker callbacks</a></span></li><li class="chapter-item expanded "><span class="chapter-link-wrapper"><a href="modules/third_party_rules_callbacks.html">Third-party rules callbacks</a></span></li><li class="chapter-item expanded "><span class="chapter-link-wrapper"><a href="modules/presence_router_callbacks.html">Presence router callbacks</a></span></li><li class="chapter-item expanded "><span class="chapter-link-wrapper"><a href="modules/account_validity_callbacks.html">Account validity callbacks</a></span></li><li class="chapter-item expanded "><span class="chapter-link-wrapper"><a href="modules/password_auth_provider_callbacks.html">Password auth provider callbacks</a></span></li><li class="chapter-item expanded "><span class="chapter-link-wrapper"><a href="modules/background_update_controller_callbacks.html">Background update controller callbacks</a></span></li><li class="chapter-item expanded "><span class="chapter-link-wrapper"><a href="modules/account_data_callbacks.html">Account data callbacks</a></span></li><li class="chapter-item expanded "><span class="chapter-link-wrapper"><a href="modules/add_extra_fields_to_client_events_unsigned.html">Add extra fields to client events unsigned section callbacks</a></span></li><li class="chapter-item expanded "><span class="chapter-link-wrapper"><a href="modules/media_repository_callbacks.html">Media repository callbacks</a></span></li><li class="chapter-item expanded "><span class="chapter-link-wrapper"><a href="modules/ratelimit_callbacks.html">Ratelimit callbacks</a></span></li><li class="chapter-item expanded "><span class="chapter-link-wrapper"><a href="modules/porting_legacy_module.html">Porting a legacy module to the new interface</a></span></li></ol></li></ol><li class="chapter-item expanded "><span class="chapter-link-wrapper"><a href="workers.html">Workers</a></span><ol class="section"><li class="chapter-item expanded "><span class="chapter-link-wrapper"><a href="synctl_workers.html">Using synctl with Workers</a></span></li><li class="chapter-item expanded "><span class="chapter-link-wrapper"><a href="systemd-with-workers/index.html">Systemd</a></span></li></ol></li></ol><li class="chapter-item expanded "><span class="chapter-link-wrapper"><a href="usage/administration/index.html">Administration</a></span><ol class="section"><li class="chapter-item expanded "><span class="chapter-link-wrapper"><a href="usage/administration/backups.html">Backups</a></span></li><li class="chapter-item expanded "><span class="chapter-link-wrapper"><a href="usage/administration/admin_api/index.html">Admin API</a></span><ol class="section"><li class="chapter-item expanded "><span class="chapter-link-wrapper"><a href="admin_api/account_validity.html">Account Validity</a></span></li><li class="chapter-item expanded "><span class="chapter-link-wrapper"><a href="usage/administration/admin_api/background_updates.html">Background Updates</a></span></li><li class="chapter-item expanded "><span class="chapter-link-wrapper"><a href="admin_api/fetch_event.html">Fetch Event</a></span></li><li class="chapter-item expanded "><span class="chapter-link-wrapper"><a href="admin_api/event_reports.html">Event Reports</a></span></li><li class="chapter-item expanded "><span class="chapter-link-wrapper"><a href="admin_api/experimental_features.html">Experimental Features</a></span></li><li class="chapter-item expanded "><span class="chapter-link-wrapper"><a href="admin_api/media_admin_api.html">Media</a></span></li><li class="chapter-item expanded "><span class="chapter-link-wrapper"><a href="admin_api/purge_history_api.html">Purge History</a></span></li><li class="chapter-item expanded "><span class="chapter-link-wrapper"><a href="admin_api/register_api.html">Register Users</a></span></li><li class="chapter-item expanded "><span class="chapter-link-wrapper"><a href="usage/administration/admin_api/registration_tokens.html">Registration Tokens</a></span></li><li class="chapter-item expanded "><span class="chapter-link-wrapper"><a href="admin_api/room_membership.html">Manipulate Room Membership</a></span></li><li class="chapter-item expanded "><span class="chapter-link-wrapper"><a href="admin_api/rooms.html">Rooms</a></span></li><li class="chapter-item expanded "><span class="chapter-link-wrapper"><a href="admin_api/scheduled_tasks.html">Scheduled tasks</a></span></li><li class="chapter-item expanded "><span class="chapter-link-wrapper"><a href="admin_api/server_notices.html">Server Notices</a></span></li><li class="chapter-item expanded "><span class="chapter-link-wrapper"><a href="admin_api/statistics.html">Statistics</a></span></li><li class="chapter-item expanded "><span class="chapter-link-wrapper"><a href="admin_api/user_admin_api.html">Users</a></span></li><li class="chapter-item expanded "><span class="chapter-link-wrapper"><a href="admin_api/version_api.html">Server Version</a></span></li><li class="chapter-item expanded "><span class="chapter-link-wrapper"><a href="usage/administration/admin_api/federation.html">Federation</a></span></li><li class="chapter-item expanded "><span class="chapter-link-wrapper"><a href="admin_api/client_server_api_extensions.html">Client-Server API Extensions</a></span></li></ol><li class="chapter-item expanded "><span class="chapter-link-wrapper"><a href="manhole.html">Manhole</a></span></li><li class="chapter-item expanded "><span class="chapter-link-wrapper"><a href="metrics-howto.html">Monitoring</a></span><ol class="section"><li class="chapter-item expanded "><span class="chapter-link-wrapper"><a href="usage/administration/monitoring/reporting_homeserver_usage_statistics.html">Reporting Homeserver Usage Statistics</a></span></li></ol><li class="chapter-item expanded "><span class="chapter-link-wrapper"><a href="usage/administration/monthly_active_users.html">Monthly Active Users</a></span></li><li class="chapter-item expanded "><span class="chapter-link-wrapper"><a href="usage/administration/understanding_synapse_through_grafana_graphs.html">Understanding Synapse Through Grafana Graphs</a></span></li><li class="chapter-item expanded "><span class="chapter-link-wrapper"><a href="usage/administration/useful_sql_for_admins.html">Useful SQL for Admins</a></span></li><li class="chapter-item expanded "><span class="chapter-link-wrapper"><a href="usage/administration/database_maintenance_tools.html">Database Maintenance Tools</a></span></li><li class="chapter-item expanded "><span class="chapter-link-wrapper"><a href="usage/administration/state_groups.html">State Groups</a></span></li><li class="chapter-item expanded "><span class="chapter-link-wrapper"><a href="usage/administration/request_log.html">Request log format</a></span></li><li class="chapter-item expanded "><span class="chapter-link-wrapper"><a href="usage/administration/admin_faq.html">Admin FAQ</a></span></li><li class="chapter-item expanded "><span class="chapter-link-wrapper"><span>Scripts</span></span></li></ol><li class="chapter-item expanded "><li class="part-title">Development</li></li><li class="chapter-item expanded "><span class="chapter-link-wrapper"><a href="development/contributing_guide.html">Contributing Guide</a></span></li><li class="chapter-item expanded "><span class="chapter-link-wrapper"><a href="code_style.html">Code Style</a></span></li><li class="chapter-item expanded "><span class="chapter-link-wrapper"><a href="development/reviews.html">Reviewing Code</a></span></li><li class="chapter-item expanded "><span class="chapter-link-wrapper"><a href="development/releases.html">Release Cycle</a></span></li><li class="chapter-item expanded "><span class="chapter-link-wrapper"><a href="development/git.html">Git Usage</a></span></li><li class="chapter-item expanded "><span class="chapter-link-wrapper"><span>Testing</span></span><ol class="section"><li class="chapter-item expanded "><span class="chapter-link-wrapper"><a href="development/demo.html">Demo scripts</a></span></li></ol><li class="chapter-item expanded "><span class="chapter-link-wrapper"><a href="opentracing.html">OpenTracing</a></span></li><li class="chapter-item expanded "><span class="chapter-link-wrapper"><a href="development/database_schema.html">Database Schemas</a></span></li><li class="chapter-item expanded "><span class="chapter-link-wrapper"><a href="development/experimental_features.html">Experimental features</a></span></li><li class="chapter-item expanded "><span class="chapter-link-wrapper"><a href="development/dependencies.html">Dependency management</a></span></li><li class="chapter-item expanded "><span class="chapter-link-wrapper"><span>Synapse Architecture</span></span><ol class="section"><li class="chapter-item expanded "><span class="chapter-link-wrapper"><a href="development/synapse_architecture/cancellation.html">Cancellation</a></span></li><li class="chapter-item expanded "><span class="chapter-link-wrapper"><a href="log_contexts.html">Log Contexts</a></span></li><li class="chapter-item expanded "><span class="chapter-link-wrapper"><a href="replication.html">Replication</a></span></li><li class="chapter-item expanded "><span class="chapter-link-wrapper"><a href="development/synapse_architecture/streams.html">Streams</a></span></li><li class="chapter-item expanded "><span class="chapter-link-wrapper"><a href="tcp_replication.html">TCP Replication</a></span></li><li class="chapter-item expanded "><span class="chapter-link-wrapper"><a href="development/synapse_architecture/faster_joins.html">Faster remote joins</a></span></li></ol><li class="chapter-item expanded "><span class="chapter-link-wrapper"><a href="development/internal_documentation/index.html">Internal Documentation</a></span><ol class="section"><li class="chapter-item expanded "><span class="chapter-link-wrapper"><span>Single Sign-On</span></span><ol class="section"><li class="chapter-item expanded "><span class="chapter-link-wrapper"><a href="development/saml.html">SAML</a></span></li><li class="chapter-item expanded "><span class="chapter-link-wrapper"><a href="development/cas.html">CAS</a></span></li></ol><li class="chapter-item expanded "><span class="chapter-link-wrapper"><a href="development/room-dag-concepts.html">Room DAG concepts</a></span></li><li class="chapter-item expanded "><span class="chapter-link-wrapper"><span>State Resolution</span></span><ol class="section"><li class="chapter-item expanded "><span class="chapter-link-wrapper"><a href="auth_chain_difference_algorithm.html">The Auth Chain Difference Algorithm</a></span></li></ol><li class="chapter-item expanded "><span class="chapter-link-wrapper"><a href="media_repository.html">Media Repository</a></span></li><li class="chapter-item expanded "><span class="chapter-link-wrapper"><a href="room_and_user_statistics.html">Room and User Statistics</a></span></li><li class="chapter-item expanded "><span class="chapter-link-wrapper"><span>Releasing</span></span><ol class="section"><li class="chapter-item expanded "><span class="chapter-link-wrapper"><a href="development/internal_documentation/release_notes_review_checklist.html">Release Notes Review Checklist</a></span></li></ol></li></ol><li class="chapter-item expanded "><span class="chapter-link-wrapper"><span>Scripts</span></span></li><li class="chapter-item expanded "><li class="part-title">Other</li></li><li class="chapter-item expanded "><span class="chapter-link-wrapper"><a href="deprecation_policy.html">Dependency Deprecation Policy</a></span></li><li class="chapter-item expanded "><span class="chapter-link-wrapper"><a href="other/running_synapse_on_single_board_computers.html">Running Synapse on a Single-Board Computer</a></span></li></ol>';
// Set the current, active page, and reveal it if it's hidden
let current_page = document.location.href.toString().split('#')[0].split('?')[0];
if (current_page.endsWith('/')) {
current_page += 'index.html';
}
const links = Array.prototype.slice.call(this.querySelectorAll('a'));
const l = links.length;
for (let i = 0; i < l; ++i) {
const link = links[i];
const href = link.getAttribute('href');
if (href && !href.startsWith('#') && !/^(?:[a-z+]+:)?\/\//.test(href)) {
link.href = path_to_root + href;
}
// The 'index' page is supposed to alias the first chapter in the book.
if (link.href === current_page
|| i === 0
&& path_to_root === ''
&& current_page.endsWith('/index.html')) {
link.classList.add('active');
let parent = link.parentElement;
while (parent) {
if (parent.tagName === 'LI' && parent.classList.contains('chapter-item')) {
parent.classList.add('expanded');
}
parent = parent.parentElement;
}
}
}
// Track and set sidebar scroll position
this.addEventListener('click', e => {
if (e.target.tagName === 'A') {
const clientRect = e.target.getBoundingClientRect();
const sidebarRect = this.getBoundingClientRect();
sessionStorage.setItem('sidebar-scroll-offset', clientRect.top - sidebarRect.top);
}
}, { passive: true });
const sidebarScrollOffset = sessionStorage.getItem('sidebar-scroll-offset');
sessionStorage.removeItem('sidebar-scroll-offset');
if (sidebarScrollOffset !== null) {
// preserve sidebar scroll position when navigating via links within sidebar
const activeSection = this.querySelector('.active');
if (activeSection) {
const clientRect = activeSection.getBoundingClientRect();
const sidebarRect = this.getBoundingClientRect();
const currentOffset = clientRect.top - sidebarRect.top;
this.scrollTop += currentOffset - parseFloat(sidebarScrollOffset);
}
} else {
// scroll sidebar to current active section when navigating via
// 'next/previous chapter' buttons
const activeSection = document.querySelector('#mdbook-sidebar .active');
if (activeSection) {
activeSection.scrollIntoView({ block: 'center' });
}
}
// Toggle buttons
const sidebarAnchorToggles = document.querySelectorAll('.chapter-fold-toggle');
function toggleSection(ev) {
ev.currentTarget.parentElement.parentElement.classList.toggle('expanded');
}
Array.from(sidebarAnchorToggles).forEach(el => {
el.addEventListener('click', toggleSection);
});
}
}
window.customElements.define('mdbook-sidebar-scrollbox', MDBookSidebarScrollbox);
// ---------------------------------------------------------------------------
// Support for dynamically adding headers to the sidebar.
(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 = [];
// This is a debugging tool for the threshold which you can enable in the console.
let thresholdDebug = false;
// 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 least 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.
const bottomAdd = Math.max(0, windowHeight - pixelsBelow - defaultDownThreshold);
let adjustedBottomAdd = bottomAdd;
// 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 / Math.max(1, maxPixelsBelow);
const clamp = Math.max(0, Math.min(1, t));
adjustedBottomAdd *= 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,
// adjustedBottomAdd will offset the threshold towards the bottom
// of the page.
const amountScrolledDown = scrollTop - lastKnownScrollPosition;
const adjustedDefault = defaultDownThreshold + adjustedBottomAdd;
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, adjustedBottomAdd - defaultDownThreshold);
threshold = Math.min(adjustedDefault, threshold + amountScrolledUp);
}
if (documentHeight <= windowHeight) {
threshold = 0;
}
if (thresholdDebug) {
const id = 'mdbook-threshold-debug-data';
let data = document.getElementById(id);
if (data === null) {
data = document.createElement('div');
data.id = id;
data.style.cssText = `
position: fixed;
top: 50px;
right: 10px;
background-color: 0xeeeeee;
z-index: 9999;
pointer-events: none;
`;
document.body.appendChild(data);
}
data.innerHTML = `
<table>
<tr><td>documentHeight</td><td>${documentHeight.toFixed(1)}</td></tr>
<tr><td>windowHeight</td><td>${windowHeight.toFixed(1)}</td></tr>
<tr><td>scrollTop</td><td>${scrollTop.toFixed(1)}</td></tr>
<tr><td>pixelsAbove</td><td>${pixelsAbove.toFixed(1)}</td></tr>
<tr><td>pixelsBelow</td><td>${pixelsBelow.toFixed(1)}</td></tr>
<tr><td>bottomAdd</td><td>${bottomAdd.toFixed(1)}</td></tr>
<tr><td>adjustedBottomAdd</td><td>${adjustedBottomAdd.toFixed(1)}</td></tr>
<tr><td>scrollingDown</td><td>${scrollingDown}</td></tr>
<tr><td>threshold</td><td>${threshold.toFixed(1)}</td></tr>
</table>
`;
drawDebugLine();
}
lastKnownScrollPosition = scrollTop;
}
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);
}
function mdbookEnableThresholdDebug() {
thresholdDebug = true;
updateThreshold();
drawDebugLine();
}
window.mdbookEnableThresholdDebug = mdbookEnableThresholdDebug;
// 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;
while (current) {
if (current.tagName === 'LI' && current.classList.contains('header-item')) {
current.classList.add('expanded');
}
current = current.parentElement;
}
}
// 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 (!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();
}
});
});
}
// Takes the nodes from the given head and copies them over to the
// destination, along with some filtering.
function filterHeader(source, dest) {
const clone = source.cloneNode(true);
clone.querySelectorAll('mark').forEach(mark => {
mark.replaceWith(...mark.childNodes);
});
dest.append(...clone.childNodes);
}
// 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 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;
}
// Build a tree of headers in the sidebar.
const stack = [];
const firstLevel = parseInt(headers[0].tagName.charAt(1));
for (let i = 1; i < firstLevel; i++) {
const ol = document.createElement('ol');
ol.classList.add('section');
if (stack.length > 0) {
stack[stack.length - 1].ol.appendChild(ol);
}
stack.push({level: i + 1, ol: ol});
}
// The level where it will start folding deeply nested headers.
const foldLevel = 3;
for (let i = 0; i < headers.length; i++) {
const header = headers[i];
const level = parseInt(header.tagName.charAt(1));
const currentLevel = stack[stack.length - 1].level;
if (level > currentLevel) {
// Begin nesting to this level.
for (let nextLevel = currentLevel + 1; nextLevel <= level; nextLevel++) {
const ol = document.createElement('ol');
ol.classList.add('section');
const last = stack[stack.length - 1];
const lastChild = last.ol.lastChild;
// Handle the case where jumping more than one nesting
// level, which doesn't have a list item to place this new
// list inside of.
if (lastChild) {
lastChild.appendChild(ol);
} else {
last.ol.appendChild(ol);
}
stack.push({level: nextLevel, ol: ol});
}
} else if (level < currentLevel) {
while (stack.length > 1 && stack[stack.length - 1].level > level) {
stack.pop();
}
}
const li = document.createElement('li');
li.classList.add('header-item');
li.classList.add('expanded');
if (level < foldLevel) {
li.classList.add('expanded');
}
const span = document.createElement('span');
span.classList.add('chapter-link-wrapper');
const a = document.createElement('a');
span.appendChild(a);
a.href = '#' + header.id;
a.classList.add('header-in-summary');
filterHeader(header.children[0], a);
a.addEventListener('click', headerThresholdClick);
const nextHeader = headers[i + 1];
if (nextHeader !== undefined) {
const nextLevel = parseInt(nextHeader.tagName.charAt(1));
if (nextLevel > level && level >= foldLevel) {
const toggle = document.createElement('a');
toggle.classList.add('chapter-fold-toggle');
toggle.classList.add('header-toggle');
toggle.addEventListener('click', () => {
li.classList.toggle('expanded');
});
const toggleDiv = document.createElement('div');
toggleDiv.textContent = '❱';
toggle.appendChild(toggleDiv);
span.appendChild(toggle);
headerToggles.push(li);
}
}
li.appendChild(span);
const currentParent = stack[stack.length - 1];
currentParent.ol.appendChild(li);
}
const onThisPage = document.createElement('div');
onThisPage.classList.add('on-this-page');
onThisPage.append(stack[0].ol);
const activeItemSpan = activeSection.parentElement;
activeItemSpan.after(onThisPage);
});
document.addEventListener('DOMContentLoaded', reloadCurrentHeader);
document.addEventListener('scroll', reloadCurrentHeader, { passive: true });
})();