// 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 = '
- 关于本书
- 进入 Rust 编程世界
- 避免从入门到放弃
- 社区和锈书
- Xobserve: 一切皆可观测
- BeatAI: 工程师 AI 入门圣经
- Rust 语言基础学习
- 1. 寻找牛刀,以便小试
❱
- 1.1. 安装 Rust 环境
- 1.2. 墙推 VSCode!
- 1.3. 认识 Cargo
- 1.4. 不仅仅是 Hello world
- 1.5. 下载依赖太慢了?
- 2. Rust 基础入门
❱
- 2.1. 变量绑定与解构
- 2.2. 基本类型
❱
- 2.2.1. 数值类型
- 2.2.2. 字符、布尔、单元类型
- 2.2.3. 语句与表达式
- 2.2.4. 函数
- 2.3. 所有权和借用
❱
- 2.3.1. 所有权
- 2.3.2. 引用与借用
- 2.4. 复合类型
❱
- 2.4.1. 字符串与切片
- 2.4.2. 元组
- 2.4.3. 结构体
- 2.4.4. 枚举
- 2.4.5. 数组
- 2.5. 流程控制
- 2.6. 模式匹配
❱
- 2.6.1. match 和 if let
- 2.6.2. 解构 Option
- 2.6.3. 模式适用场景
- 2.6.4. 全模式列表
- 2.7. 方法 Method
- 2.8. 泛型和特征
❱
- 2.8.1. 泛型 Generics
- 2.8.2. 特征 Trait
- 2.8.3. 特征对象
- 2.8.4. 进一步深入特征
- 2.9. 集合类型
❱
- 2.9.1. 动态数组 Vector
- 2.9.2. KV 存储 HashMap
- 2.10. 认识生命周期
- 2.11. 返回值和错误处理
❱
- 2.11.1. panic! 深入剖析
- 2.11.2. 返回值 Result 和?
- 2.12. 包和模块
❱
- 2.12.1. 包 Crate
- 2.12.2. 模块 Module
- 2.12.3. 使用 use 引入模块及受限可见性
- 2.13. 注释和文档
- 2.14. 格式化输出
- 3. 入门实战:文件搜索工具
❱
- 3.1. 基本功能
- 3.2. 增加模块化和错误处理
- 3.3. 测试驱动开发
- 3.4. 使用环境变量
- 3.5. 重定向错误信息的输出
- 3.6. 使用迭代器来改进程序(可选)
- Rust 语言进阶学习
- 4. Rust 高级进阶
❱
- 4.1. 生命周期
❱
- 4.1.1. 深入生命周期
- 4.1.2. &'static 和 T: 'static
- 4.2. 函数式编程: 闭包、迭代器
❱
- 4.2.1. 闭包 Closure
- 4.2.2. 迭代器 Iterator
- 4.3. 深入类型
❱
- 4.3.1. 类型转换
- 4.3.2. newtype 和 类型别名
- 4.3.3. Sized 和不定长类型 DST
- 4.3.4. 枚举和整数
- 4.4. 智能指针
❱
- 4.4.1. Box<T> 堆对象分配
- 4.4.2. Deref 解引用
- 4.4.3. Drop 释放资源
- 4.4.4. Rc 与 Arc 实现 1vN 所有权机制
- 4.4.5. Cell 与 RefCell 内部可变性
- 4.5. 循环引用与自引用
❱
- 4.5.1. Weak 与循环引用
- 4.5.2. 结构体中的自引用
- 4.6. 多线程并发编程
❱
- 4.6.1. 并发和并行
- 4.6.2. 使用多线程
- 4.6.3. 线程同步:消息传递
- 4.6.4. 线程同步:锁、Condvar 和信号量
- 4.6.5. 线程同步:Atomic 原子操作与内存顺序
- 4.6.6. 基于 Send 和 Sync 的线程安全
- 4.7. 全局变量
- 4.8. 错误处理
- 4.9. Unsafe Rust
❱
- 4.9.1. 五种兵器
- 4.9.2. 内联汇编
- 4.10. Macro 宏编程
- 4.11. async/await 异步编程
❱
- 4.11.1. async 编程入门
- 4.11.2. 底层探秘: Future 执行与任务调度
- 4.11.3. 定海神针 Pin 和 Unpin
- 4.11.4. async/await 和 Stream 流处理
- 4.11.5. 同时运行多个 Future
- 4.11.6. 一些疑难问题的解决办法
- 4.11.7. 实践应用:Async Web 服务器
- 5. 进阶实战1: 实现一个 web 服务器
❱
- 5.1. 单线程版本
- 5.2. 多线程版本
- 5.3. 优雅关闭和资源清理
- 6. 进阶实战2: 实现一个简单 Redis
❱
- 6.1. tokio 概览
- 6.2. 使用初印象
- 6.3. 创建异步任务
- 6.4. 共享状态
- 6.5. 消息传递
- 6.6. I/O
- 6.7. 解析数据帧
- 6.8. 深入 async
- 6.9. select
- 6.10. 类似迭代器的 Stream
- 6.11. 优雅的关闭
- 6.12. 异步跟同步共存
- 7. Rust 难点攻关
❱
- 7.1. 切片和切片引用
- 7.2. Eq 和 PartialEq
- 7.3. String、&str 和 str TODO
- 7.4. 作用域、生命周期和 NLL TODO
- 7.5. move、Copy 和 Clone TODO
- 7.6. 裸指针、引用和智能指针 TODO
- 常用工具链
- 8. 自动化测试
❱
- 8.1. 编写测试及控制执行
- 8.2. 单元测试和集成测试
- 8.3. 断言 assertion
- 8.4. 用 GitHub Actions 进行持续集成
- 8.5. 基准测试 benchmark
- 9. Cargo 使用指南
❱
- 9.1. 上手使用
- 9.2. 基础指南
❱
- 9.2.1. 为何会有 Cargo
- 9.2.2. 下载并构建 Package
- 9.2.3. 添加依赖
- 9.2.4. Package 目录结构
- 9.2.5. Cargo.toml vs Cargo.lock
- 9.2.6. 测试和 CI
- 9.2.7. Cargo 缓存
- 9.2.8. Build 缓存
- 9.3. 进阶指南
❱
- 9.3.1. 指定依赖项
- 9.3.2. 依赖覆盖
- 9.3.3. Cargo.toml 清单详解
- 9.3.4. Cargo Target
- 9.3.5. 工作空间 Workspace
- 9.3.6. 条件编译 Features
❱
- 9.3.6.1. Features 示例
- 9.3.7. 发布配置 Profile
- 9.3.8. 通过 config.toml 对 Cargo 进行配置
- 9.3.9. 发布到 crates.io
- 9.3.10. 构建脚本 build.rs
❱
- 9.3.10.1. 构建脚本示例
- 开发实践
- 10. 企业落地实践
❱
- 10.1. AWS 为何这么喜欢 Rust?
- 11. 日志和监控
❱
- 11.1. 日志详解
- 11.2. 日志门面 log
- 11.3. 使用 tracing 记录日志
- 11.4. 自定义 tracing 的输出格式
- 11.5. 监控
❱
- 11.5.1. 可观测性
- 11.5.2. 分布式追踪
- 12. Rust 最佳实践
❱
- 12.1. 日常开发三方库精选
- 12.2. 命名规范
- 12.3. 面试经验
- 12.4. 代码开发实践 todo
- 13. 手把手带你实现链表
❱
- 13.1. 我们到底需不需要链表
- 13.2. 不太优秀的单向链表:栈
❱
- 13.2.1. 数据布局
- 13.2.2. 基本操作
- 13.2.3. 最后实现
- 13.3. 还可以的单向链表
❱
- 13.3.1. 优化类型定义
- 13.3.2. 定义 Peek 函数
- 13.3.3. IntoIter 和 Iter
- 13.3.4. IterMut 以及完整代码
- 13.4. 持久化单向链表
❱
- 13.4.1. 数据布局和基本操作
- 13.4.2. Drop、Arc 及完整代码
- 13.5. 不咋样的双端队列
❱
- 13.5.1. 数据布局和基本操作
- 13.5.2. Peek
- 13.5.3. 基本操作的对称镜像
- 13.5.4. 迭代器
- 13.5.5. 最终代码
- 13.6. 不错的 unsafe 队列
❱
- 13.6.1. 数据布局
- 13.6.2. 基本操作
- 13.6.3. Miri
- 13.6.4. 栈借用
- 13.6.5. 测试栈借用
- 13.6.6. 数据布局 2
- 13.6.7. 额外的操作
- 13.6.8. 最终代码
- 13.7. 生产级的双向 unsafe 队列
❱
- 13.7.1. 数据布局
- 13.7.2. 型变与子类型
- 13.7.3. 基础结构
- 13.7.4. 恐慌与安全
- 13.7.5. 无聊的组合
- 13.7.6. 其它特征
- 13.7.7. 测试
- 13.7.8. Send,Sync和编译测试
- 13.7.9. 实现游标
- 13.7.10. 测试游标
- 13.7.11. 最终代码
- 13.8. 使用高级技巧实现链表
❱
- 13.8.1. 双单向链表
- 13.8.2. 栈上的链表
- 攻克编译错误
- 14. 征服编译错误
❱
- 14.1. 对抗编译检查
❱
- 14.1.1. 生命周期
❱
- 14.1.1.1. 生命周期过大-01
- 14.1.1.2. 生命周期过大-02
- 14.1.1.3. 循环中的生命周期
- 14.1.1.4. 闭包碰到特征对象-01
- 14.1.2. 重复借用
❱
- 14.1.2.1. 同时在函数内外使用引用
- 14.1.2.2. 智能指针引起的重复借用错误
- 14.1.3. 类型未限制(todo)
- 14.1.4. 幽灵数据(todo)
- 14.2. Rust 常见陷阱
❱
- 14.2.1. for 循环中使用外部数组
- 14.2.2. 线程类型导致的栈溢出
- 14.2.3. 算术溢出导致的 panic
- 14.2.4. 闭包中奇怪的生命周期
- 14.2.5. 可变变量不可变?
- 14.2.6. 可变借用失败引发的深入思考
- 14.2.7. 不太勤快的迭代器
- 14.2.8. 奇怪的序列 x..y
- 14.2.9. 无处不在的迭代器
- 14.2.10. 线程间传递消息导致主线程无法结束
- 14.2.11. 警惕 UTF-8 引发的性能隐患
- 性能优化
- 15. Rust 性能优化 todo
❱
- 15.1. 深入内存 todo
❱
- 15.1.1. 指针和引用 todo
- 15.1.2. 未初始化内存 todo
- 15.1.3. 内存分配 todo
- 15.1.4. 内存布局 todo
- 15.1.5. 虚拟内存 todo
- 15.2. 性能调优 doing
❱
- 15.2.1. 字符串操作性能
- 15.2.2. 深入理解 move
- 15.2.3. 糟糕的提前优化 todo
- 15.2.4. Clone 和 Copy todo
- 15.2.5. 减少 Runtime check(todo)
- 15.2.6. CPU 缓存性能优化 todo
- 15.2.7. 计算性能优化 todo
- 15.2.8. 堆和栈 todo
- 15.2.9. 内存 allocator todo
- 15.2.10. 常用性能测试工具 todo
- 15.2.11. Enum 内存优化 todo
- 15.3. 编译优化 todo
❱
- 15.3.1. LLVM todo
- 15.3.2. 常见属性标记 todo
- 15.3.3. 提升编译速度 todo
- 15.3.4. 编译器优化 todo
❱
- 15.3.4.1. Option 枚举 todo
- 附录
- 16. Appendix
❱
- 16.1. 关键字
- 16.2. 运算符与符号
- 16.3. 表达式
- 16.4. 派生特征 trait
- 16.5. prelude 模块 todo
- 16.6. Rust 版本说明
- 16.7. Rust 历次版本更新解读
❱
- 16.7.1. 1.58
- 16.7.2. 1.59
- 16.7.3. 1.60
- 16.7.4. 1.61
- 16.7.5. 1.62
- 16.7.6. 1.63
- 16.7.7. 1.64
- 16.7.8. 1.65
- 16.7.9. 1.66
- 16.7.10. 1.67
- 16.7.11. 1.68
- 16.7.12. 1.69
- 16.7.13. 1.70
- 16.7.14. 1.71
- 16.7.15. 1.72
- 16.7.16. 1.73
- 16.7.17. 1.74
- 16.7.18. 1.75
- 16.7.19. 1.76
- 16.7.20. 1.77
- 16.7.21. 1.78
- 16.7.22. 1.79
- 16.7.23. 1.80
- 16.7.24. 1.81
- 16.7.25. 1.82
- 16.7.26. 1.83
- 16.7.27. 1.84
- 16.7.28. 1.85
- 16.7.29. 1.86
- 16.7.30. 1.87
- 16.7.31. 1.88
- 16.7.32. 1.89
';
// 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 = `
| documentHeight | ${documentHeight.toFixed(1)} |
| windowHeight | ${windowHeight.toFixed(1)} |
| scrollTop | ${scrollTop.toFixed(1)} |
| pixelsAbove | ${pixelsAbove.toFixed(1)} |
| pixelsBelow | ${pixelsBelow.toFixed(1)} |
| bottomAdd | ${bottomAdd.toFixed(1)} |
| adjustedBottomAdd | ${adjustedBottomAdd.toFixed(1)} |
| scrollingDown | ${scrollingDown} |
| threshold | ${threshold.toFixed(1)} |
`;
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 .
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 });
})();