This is still not perfect, but it reduces the shift that happens when navigating between pages that have and don't have the table of contents. It also tries to reduce the theme flicker that happens by moving its loading to an earlier moment. Release Notes: - N/A
421 lines
18 KiB
Handlebars
421 lines
18 KiB
Handlebars
<!DOCTYPE HTML>
|
|
<html lang="{{ language }}" dir="{{ text_direction }}">
|
|
<head>
|
|
<!-- Book generated using mdBook -->
|
|
<meta charset="UTF-8">
|
|
<!-- Theme initialization - must run before any CSS loads to prevent flicker -->
|
|
<script>
|
|
(function() {
|
|
var theme;
|
|
try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
|
|
if (theme === null || theme === undefined) {
|
|
theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
|
|
}
|
|
var html = document.documentElement;
|
|
html.setAttribute('data-theme', theme);
|
|
html.setAttribute('data-color-scheme', theme);
|
|
html.className = theme;
|
|
})();
|
|
</script>
|
|
<title>{{ title }}</title>
|
|
{{#if is_print }}
|
|
<meta name="robots" content="noindex">
|
|
{{/if}}
|
|
{{#if base_url}}
|
|
<base href="{{ base_url }}">
|
|
{{/if}}
|
|
|
|
|
|
<!-- Custom HTML head -->
|
|
{{> head}}
|
|
|
|
<meta name="description" content="#description#">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
<meta name="theme-color" content="#ffffff">
|
|
|
|
<link rel="shortcut icon" href="{{ path_to_root }}favicon.png">
|
|
<link rel="stylesheet" href="{{ path_to_root }}css/variables.css">
|
|
<link rel="stylesheet" href="{{ path_to_root }}css/general.css">
|
|
<link rel="stylesheet" href="{{ path_to_root }}css/chrome.css">
|
|
{{#if print_enable}}
|
|
<link rel="stylesheet" href="{{ path_to_root }}css/print.css" media="print">
|
|
{{/if}}
|
|
|
|
<!-- Fonts -->
|
|
<link rel="stylesheet" href="{{ path_to_root }}FontAwesome/css/font-awesome.css">
|
|
{{#if copy_fonts}}
|
|
<link rel="stylesheet" href="{{ path_to_root }}fonts/fonts.css">
|
|
{{/if}}
|
|
|
|
<!-- Highlight.js Stylesheets -->
|
|
<link rel="stylesheet" href="{{ path_to_root }}highlight.css">
|
|
<link rel="stylesheet" href="{{ path_to_root }}tomorrow-night.css">
|
|
<link rel="stylesheet" href="{{ path_to_root }}ayu-highlight.css">
|
|
|
|
<!-- Custom theme stylesheets -->
|
|
{{#each additional_css}}
|
|
<link rel="stylesheet" href="{{ ../path_to_root }}{{ this }}">
|
|
{{/each}}
|
|
|
|
{{#if mathjax_support}}
|
|
<!-- MathJax -->
|
|
<script async src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.1/MathJax.js?config=TeX-AMS-MML_HTMLorMML"></script>
|
|
{{/if}}
|
|
</head>
|
|
<body class="no-js">
|
|
<div id="body-container">
|
|
<div class="noise-pattern" style="background-image: url('https://cdn.zed.dev/images/noise.png');"></div>
|
|
|
|
<!-- Provide site root to javascript -->
|
|
<script>
|
|
var path_to_root = "{{ path_to_root }}";
|
|
var default_theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "{{ preferred_dark_theme }}" : "{{ default_theme }}";
|
|
// Mark as JS-enabled
|
|
document.body.classList.remove('no-js');
|
|
document.body.classList.add('js');
|
|
</script>
|
|
|
|
<header class="header-bar">
|
|
<div class="left-container">
|
|
<a href="/" class="logo-nav">
|
|
<img src="https://zed.dev/logo_icon.webp" class="icon-logo-img" alt="Zed Industries" style="height: 26px;">
|
|
</a>
|
|
<button id="sidebar-toggle" class="icon-button ib-hidden-desktop" type="button" title="Toggle Table of Contents" aria-label="Toggle Table of Contents" aria-controls="sidebar" aria-expanded="false">
|
|
<i class="fa fa-bars"></i>
|
|
</button>
|
|
</div>
|
|
{{#if search_enabled}}
|
|
<button id="search-toggle" class="search-button" type="button" aria-expanded="false" aria-keyshortcuts="S" aria-controls="searchbar">
|
|
<i class="icon fa fa-search"></i>
|
|
<span class="search-content-desktop">
|
|
<span class="placeholder">Search docs…</span>
|
|
<kbd>S</kbd>
|
|
</span>
|
|
<span class="search-content-mobile">
|
|
<span class="placeholder">Search…</span>
|
|
</span>
|
|
</button>
|
|
{{/if}}
|
|
<div class="right-container">
|
|
<button id="theme-toggle" class="icon-button" type="button" title="Change Theme" aria-label="Change Theme" aria-haspopup="true" aria-expanded="false" aria-controls="theme-list">
|
|
<i class="fa fa-paint-brush"></i>
|
|
</button>
|
|
|
|
<ul id="theme-list" class="theme-popup" aria-label="Themes" role="menu">
|
|
<li role="none"><button role="menuitem" class="theme" id="light">Light</button></li>
|
|
<li role="none"><button role="menuitem" class="theme" id="dark">Dark</button></li>
|
|
</ul>
|
|
|
|
<button id="copy-markdown-toggle" class="icon-button ib-hidden-mobile" type="button" title="Copy Page as Markdown" aria-label="Copy page as markdown">
|
|
<i class="fa fa-copy"></i>
|
|
</button>
|
|
|
|
<a class="download-button" href="https://zed.dev/download" title="Download Zed" aria-label="Download Zed">
|
|
Download
|
|
</a>
|
|
{{#if git_repository_url}}
|
|
<a href="{{git_repository_url}}" title="Git repository" aria-label="Git repository">
|
|
<i id="git-repository-button" class="fa {{git_repository_icon}}"></i>
|
|
</a>
|
|
{{/if}}
|
|
{{#if git_repository_edit_url}}
|
|
<a href="{{git_repository_edit_url}}" title="Suggest an edit" aria-label="Suggest an edit">
|
|
<i id="git-edit-button" class="fa fa-edit"></i>
|
|
</a>
|
|
{{/if}}
|
|
</div>
|
|
</header>
|
|
|
|
<div id="page-wrapper" class="page-wrapper">
|
|
|
|
{{#if search_enabled}}
|
|
<div class="search-container">
|
|
<div id="search-wrapper" class="search-modal hidden">
|
|
<form id="searchbar-outer" class="searchbar-outer">
|
|
<input type="search" id="searchbar" name="searchbar" placeholder="Search…" aria-controls="searchresults-outer" aria-describedby="searchresults-header">
|
|
</form>
|
|
<div id="searchresults-outer" class="searchresults-outer">
|
|
<div id="searchresults-header" class="searchresults-header"></div>
|
|
<ul id="searchresults">
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{{/if}}
|
|
|
|
<nav id="sidebar" class="sidebar" aria-label="Table of contents">
|
|
<div class="sidebar-scrollbox">
|
|
{{#toc}}{{/toc}}
|
|
</div>
|
|
<div style="display: none;" id="sidebar-resize-handle" class="sidebar-resize-handle">
|
|
<div class="sidebar-resize-indicator"></div>
|
|
</div>
|
|
</nav>
|
|
|
|
<!-- Mobile sidebar toggle -->
|
|
<script>
|
|
(function() {
|
|
var sidebarToggle = document.getElementById('sidebar-toggle');
|
|
var sidebar = document.getElementById('sidebar');
|
|
|
|
sidebarToggle.addEventListener('click', function() {
|
|
var isOpen = document.body.classList.toggle('sidebar-open');
|
|
sidebarToggle.setAttribute('aria-expanded', isOpen);
|
|
sidebar.setAttribute('aria-hidden', !isOpen);
|
|
});
|
|
|
|
// Close sidebar when clicking a link inside it (on mobile)
|
|
sidebar.addEventListener('click', function(e) {
|
|
if (e.target.tagName === 'A' && window.innerWidth < 620) {
|
|
document.body.classList.remove('sidebar-open');
|
|
sidebarToggle.setAttribute('aria-expanded', 'false');
|
|
sidebar.setAttribute('aria-hidden', 'true');
|
|
}
|
|
});
|
|
})();
|
|
</script>
|
|
|
|
<!-- Search backdrop handlers -->
|
|
<script>
|
|
(function() {
|
|
var searchWrapper = document.getElementById('search-wrapper');
|
|
var searchContainer = document.querySelector('.search-container');
|
|
var searchResults = document.getElementById('searchresults');
|
|
|
|
if (!searchWrapper || !searchContainer) return;
|
|
|
|
searchContainer.addEventListener('click', function(e) {
|
|
if (e.target === searchContainer) {
|
|
searchWrapper.classList.add('hidden');
|
|
}
|
|
});
|
|
|
|
if (searchResults) {
|
|
searchResults.addEventListener('click', function(e) {
|
|
if (e.target.tagName === 'A' || e.target.closest('a')) {
|
|
searchWrapper.classList.add('hidden');
|
|
}
|
|
});
|
|
}
|
|
})();
|
|
</script>
|
|
|
|
<!-- Insert section spacers and apply collapsed state before scroll restoration to prevent layout shift/flicker -->
|
|
<script>
|
|
(function() {
|
|
var chapterList = document.querySelector('#sidebar ol.chapter');
|
|
if (!chapterList) return;
|
|
|
|
var collapsedSections = [];
|
|
try {
|
|
var stored = sessionStorage.getItem('sidebar-collapsed-sections');
|
|
if (stored) {
|
|
collapsedSections = JSON.parse(stored);
|
|
}
|
|
} catch (e) {}
|
|
|
|
var partTitles = chapterList.querySelectorAll('li.part-title');
|
|
var previousPartTitle = null;
|
|
|
|
partTitles.forEach(function(partTitle, index) {
|
|
partTitle._sectionName = partTitle.textContent.trim();
|
|
|
|
// Insert a spacer before this part-title (except for the first one)
|
|
if (index > 0) {
|
|
var spacer = document.createElement('li');
|
|
spacer.className = 'section-spacer';
|
|
partTitle.parentNode.insertBefore(spacer, partTitle);
|
|
|
|
if (previousPartTitle) {
|
|
previousPartTitle._spacerAfter = spacer;
|
|
}
|
|
}
|
|
|
|
var isCollapsed = collapsedSections.includes(partTitle._sectionName);
|
|
if (isCollapsed) {
|
|
// Hide all siblings until next part-title
|
|
var sibling = partTitle.nextElementSibling;
|
|
while (sibling && !sibling.classList.contains('part-title')) {
|
|
sibling.classList.add('section-hidden');
|
|
sibling = sibling.nextElementSibling;
|
|
}
|
|
// Hide the spacer after this section (will be set on next iteration)
|
|
partTitle._isCollapsed = true;
|
|
}
|
|
|
|
// If previous section was collapsed, hide its spacer
|
|
if (previousPartTitle && previousPartTitle._isCollapsed && previousPartTitle._spacerAfter) {
|
|
previousPartTitle._spacerAfter.classList.add('section-hidden');
|
|
}
|
|
|
|
previousPartTitle = partTitle;
|
|
});
|
|
|
|
// Handle the last section's spacer if it was collapsed
|
|
if (previousPartTitle && previousPartTitle._isCollapsed && previousPartTitle._spacerAfter) {
|
|
previousPartTitle._spacerAfter.classList.add('section-hidden');
|
|
}
|
|
})();
|
|
</script>
|
|
|
|
<!-- Track and set sidebar scroll position -->
|
|
<script>
|
|
var sidebarScrollbox = document.querySelector('#sidebar .sidebar-scrollbox');
|
|
sidebarScrollbox.addEventListener('click', function(e) {
|
|
if (e.target.tagName === 'A') {
|
|
sessionStorage.setItem('sidebar-scroll', sidebarScrollbox.scrollTop);
|
|
}
|
|
}, { passive: true });
|
|
var sidebarScrollTop = sessionStorage.getItem('sidebar-scroll');
|
|
sessionStorage.removeItem('sidebar-scroll');
|
|
if (sidebarScrollTop) {
|
|
// preserve sidebar scroll position when navigating via links within sidebar
|
|
sidebarScrollbox.scrollTop = sidebarScrollTop;
|
|
} else {
|
|
// scroll sidebar to current active section when navigating via "next/previous chapter" buttons
|
|
var activeSection = document.querySelector('#sidebar .active');
|
|
if (activeSection) {
|
|
activeSection.scrollIntoView({ block: 'center' });
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<div class="page">
|
|
<div id="content" class="content">
|
|
<main>
|
|
{{{ content }}}
|
|
<div class="footer-buttons">
|
|
{{#previous}}
|
|
<a rel="prev" href="{{ path_to_root }}{{link}}" class="footer-button" title="{{title}}">
|
|
<i class="fa fa-angle-left"></i>
|
|
{{title}}
|
|
</a>
|
|
{{/previous}}
|
|
{{#next}}
|
|
<a rel="next" href="{{ path_to_root }}{{link}}" class="footer-button" title="{{title}}">
|
|
{{title}}
|
|
<i class="fa fa-angle-right"></i>
|
|
</a>
|
|
{{/next}}
|
|
</div>
|
|
</main>
|
|
<div class="toc-container">
|
|
<nav class="pagetoc"></nav>
|
|
</div>
|
|
<!-- Immediately detect if page has headins that are not h1 to prevent flicker -->
|
|
<script>
|
|
(function() {
|
|
var tocContainer = document.querySelector('.toc-container');
|
|
var headers = document.querySelectorAll('.header');
|
|
var hasNonH1Headers = false;
|
|
for (var i = 0; i < headers.length; i++) {
|
|
var parent = headers[i].parentElement;
|
|
if (parent && !parent.tagName.toLowerCase().startsWith('h1')) {
|
|
hasNonH1Headers = true;
|
|
break;
|
|
}
|
|
}
|
|
if (hasNonH1Headers) {
|
|
tocContainer.classList.add('has-toc');
|
|
} else {
|
|
tocContainer.classList.add('no-toc');
|
|
}
|
|
})();
|
|
</script>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{{#if live_reload_endpoint}}
|
|
<!-- Livereload script (if served using the cli tool) -->
|
|
<script>
|
|
const wsProtocol = location.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
const wsAddress = wsProtocol + "//" + location.host + "/" + "{{{live_reload_endpoint}}}";
|
|
const socket = new WebSocket(wsAddress);
|
|
socket.onmessage = function (event) {
|
|
if (event.data === "reload") {
|
|
socket.close();
|
|
location.reload();
|
|
}
|
|
};
|
|
|
|
window.onbeforeunload = function() {
|
|
socket.close();
|
|
}
|
|
</script>
|
|
{{/if}}
|
|
|
|
{{#if playground_line_numbers}}
|
|
<script>
|
|
window.playground_line_numbers = true;
|
|
</script>
|
|
{{/if}}
|
|
|
|
{{#if playground_copyable}}
|
|
<script>
|
|
window.playground_copyable = true;
|
|
</script>
|
|
{{/if}}
|
|
|
|
{{#if playground_js}}
|
|
<script src="{{ path_to_root }}ace.js"></script>
|
|
<script src="{{ path_to_root }}editor.js"></script>
|
|
<script src="{{ path_to_root }}mode-rust.js"></script>
|
|
<script src="{{ path_to_root }}theme-dawn.js"></script>
|
|
<script src="{{ path_to_root }}theme-tomorrow_night.js"></script>
|
|
{{/if}}
|
|
|
|
{{#if search_js}}
|
|
<script src="{{ path_to_root }}elasticlunr.min.js"></script>
|
|
<script src="{{ path_to_root }}mark.min.js"></script>
|
|
<script src="{{ path_to_root }}searcher.js"></script>
|
|
{{/if}}
|
|
|
|
<script src="{{ path_to_root }}clipboard.min.js"></script>
|
|
<script src="{{ path_to_root }}highlight.js"></script>
|
|
<script src="{{ path_to_root }}book.js"></script>
|
|
|
|
<!-- Custom JS scripts -->
|
|
{{#each additional_js}}
|
|
<script src="{{ ../path_to_root }}{{this}}"></script>
|
|
{{/each}}
|
|
|
|
{{#if is_print}}
|
|
{{#if mathjax_support}}
|
|
<script>
|
|
window.addEventListener('load', function() {
|
|
MathJax.Hub.Register.StartupHook('End', function() {
|
|
window.setTimeout(window.print, 100);
|
|
});
|
|
});
|
|
</script>
|
|
{{else}}
|
|
<script>
|
|
window.addEventListener('load', function() {
|
|
window.setTimeout(window.print, 100);
|
|
});
|
|
</script>
|
|
{{/if}}
|
|
{{/if}}
|
|
|
|
<!-- Amplitude Analytics -->
|
|
<script>
|
|
(function() {
|
|
var amplitudeKey = '#amplitude_key#';
|
|
if (amplitudeKey && amplitudeKey.indexOf('#') === -1) {
|
|
var script = document.createElement('script');
|
|
script.src = 'https://cdn.amplitude.com/script/' + amplitudeKey + '.js';
|
|
script.onload = function() {
|
|
window.amplitude.init(amplitudeKey, {
|
|
fetchRemoteConfig: true,
|
|
autocapture: true
|
|
});
|
|
};
|
|
document.head.appendChild(script);
|
|
}
|
|
})();
|
|
</script>
|
|
</div>
|
|
</body>
|
|
</html>
|