版本:2.13 用途:本文档记录扩展主要行为、交互、配置项与边界情况,作为后续重构和回归验证的功能基线。 更新日期:2026-07-10
功能描述:从内容容器中提取 H1–H6 标准 HTML 标题元素,构建目录列表。
用户故事:作为一个阅读长篇文章的用户,我希望扩展能自动识别页面中的所有标题,以便在目录面板中快速跳转到任意章节。
验收标准:
- Given 页面内容容器中包含 H1–H6 标签,When 扩展初始化完成,Then 目录列表中应出现对应的所有标题条目,且顺序与页面文档顺序一致。
- Given 某个标题的 CSS 样式为
display: none或visibility: hidden,When 扩展提取标题,Then 该标题不应出现在目录中。 - Given 标题元素位于
nav、header、footer、.sidebar等排除容器内,When 扩展提取标题,Then 该标题不应出现在目录中。 - Given 同一标题文本、标签名、offsetTop 组合已被处理过,When 再次触发更新,Then 该标题不应被重复加入目录(去重机制)。
- Given 页面无任何标题,When 扩展生成目录,Then 目录面板内显示"No headers found"空状态提示,包含图标、标题文字与说明段落。
边界情况:
- 标题
textContent为空字符串时,跳过该条目,不生成链接。 - 标题没有
id属性时,扩展自动生成 id:将文本转小写后把非字母数字字符替换为-(如Hello World!→hello-world-)。 - 重构时需保留去重集合
lastProcessedHeaders在每次updateTOC前清空的逻辑。
功能描述:除 H1–H6 外,还识别 class 含 title、heading、header 关键词的元素,按字体大小与粗细推断层级。
用户故事:作为使用自定义 CSS 框架(无原生标题标签)的文档网站读者,我希望扩展能识别视觉上的"标题",以便生成有意义的目录。
验收标准:
- Given 页面存在 class 含
title/heading/header的元素,且fontSize >= 16px、fontWeight >= 500,When 扩展提取标题,Then 该元素应出现在目录中。 - Given 自定义标题元素字体尺寸
>= 24px,When 构建目录,Then 层级映射为 level-1。 - Given 自定义标题元素字体尺寸在 20–23px,When 构建目录,Then 层级映射为 level-2。
- Given 自定义标题元素字体尺寸在 18–19px,When 构建目录,Then 层级映射为 level-3。
- Given 自定义标题元素字体尺寸在 16–17px,When 构建目录,Then 层级映射为 level-4。
- Given 自定义标题元素字体尺寸低于 16px 或 fontWeight 低于 500,When 扩展提取,Then 该元素不被视为自定义标题。
边界情况:
- 自定义标题与标准 H 标签合并后,按文档位置排序(compareDocumentPosition)。
- 自定义标题同样受排除容器规则约束。
功能描述:目录条目按标题层级使用收敛后的 padding 缩进,最多支持 6 级;level-2/3 每级增加 12px,level-4/5 每级增加 8px,level-6 与 level-5 对齐。
用户故事:作为阅读多层次文档的用户,我希望目录能以视觉缩进反映文章的层级结构,以便一眼判断章节的从属关系。
验收标准:
- Given 目录中存在 level-1 至 level-6 的条目,When 面板展开,Then 链接左 padding 依次为 8px、20px、32px、40px、48px、48px。
- Given 目录列表内容超出面板高度(max-height: calc(100% - 60px)),When 面板展开,Then 列表区域出现纵向滚动条,支持滚动浏览。
功能描述:滚动页面时,目录中与当前视口最近的标题对应条目高亮显示,并附带左侧指示条。
用户故事:作为正在阅读长文章的用户,我希望目录能实时标出我当前所读的章节,以便随时掌握阅读位置。
验收标准:
- Given 目录面板已展开且页面包含多个标题,When 用户向下滚动使某标题进入视口顶部(top <= 100px),Then 该标题对应的目录条目获得
activeclass,其他条目移除activeclass。 - Given 用户点击某个目录链接触发平滑滚动,When 滚动结束,Then 被点击的条目立即获得
activeclass(点击时即时响应,不等待滚动结束)。 - Given 活动条目存在,When 渲染,Then 条目链接文字颜色为
--toc-accent(默认 #0969da),背景色为--toc-highlight-active,字重 500,并按层级显示 2px 左侧指示条。 - Given 没有任何标题的 top <= 100px(用户在页面顶部),When 调用 updateActiveHeader,Then 不更新任何 active 状态(activeHeader 为 null)。
边界情况:
- 距离计算使用
Math.abs(rect.top),取最小距离且rect.top <= 100的标题;页面顶部时无标题满足条件,active 状态保持不变。 - 滚动监听通过
requestAnimationFrame节流,避免高频触发。
功能描述:目录面板仅在满足"已滚动超过 N 屏"且"标题数量 >= 最低标题数"两个条件时才显示。
用户故事:作为浏览包含少量内容或短页面的用户,我希望扩展不要在不必要的情况下显示目录面板,以免干扰阅读。
验收标准:
- Given
minHeaders = 3,showAfterScrollScreens = 1,页面有 5 个标题,When 用户向下滚动超过 1 个屏幕高度,Then 目录容器display: flex。 - Given 页面标题数量少于
minHeaders设定值,When 用户无论滚动多少,Then 目录容器保持display: none。 - Given 用户未滚动超过阈值(
scrollTop <= innerHeight * showAfterScrollScreens),When 页面加载完成,Then 目录容器保持display: none。 - Given 用户滚动超过阈值后又向上滚回顶部,When scroll 事件触发,Then 目录容器重新隐藏(
display: none)。
边界情况:
showAfterScrollScreens可配置为 0,此时页面一加载即可显示(只要标题数量满足)。minHeaders可配置为 0,此时标题数量不作限制。- 滚动位置取
document.body.scrollTop + document.documentElement.scrollTop之和,兼容不同浏览器滚动模型。
功能描述:点击目录条目链接后,页面以平滑动画滚动至对应标题位置,而不是瞬间跳转。
用户故事:作为阅读文章的用户,我希望点击目录链接时页面平滑滚动到对应章节,以便保持阅读上下文感知。
验收标准:
- Given 用户点击某个目录链接,When click 事件触发,Then 调用
header.scrollIntoView({ behavior: 'smooth' }),页面平滑滚动至目标标题。 - Given 用户点击目录链接,When click 事件触发,Then 浏览器默认锚点跳转行为(
e.preventDefault())被阻止。 - Given 点击后平滑滚动开始,When 滚动进行中,Then 被点击条目立即获得 active /
aria-current,保持 900ms 后再交回滚动观察器。
功能描述:在 themePreset = 'barcode' 且 barcodePreview = 'wheel' 时,TOC 以透明边缘 rail 展示章节短横线;hover 时短横线向外产生 wave 延展,标题 track 在固定观察窗内滚动。
用户故事:作为沉浸式阅读长文的用户,我希望页面边缘只保留低侵扰的位置感知,但在我主动悬停时能快速看清对应章节标题。
验收标准:
- Given TOC 位置为右侧,When 用户悬停某个 rail item,Then 高亮短横线向左侧正文方向延展,预览气泡显示在高亮条左侧且不遮挡 rail。
- Given TOC 位置为左侧,When 用户悬停某个 rail item,Then 高亮短横线向右侧正文方向延展,预览气泡显示在高亮条右侧且不遮挡 rail。
- Given 用户在 rail 上移动鼠标,When hover wave 更新,Then
#github-toc本体不发生左右位移,只更新相关短横线的 transform 与宽度表现。 - Given 预览气泡已显示,When 检查 DOM,Then
.toc-rail-preview作为document.body子节点使用position: fixed定位,避免被 transform 祖先影响。 - Given rail 附近页面背景为浅色条带或深色 surface,When 自适应主题执行,Then rail、独立回顶按钮与预览气泡使用克制的局部 CSS 变量保持可读,rail 本体仍为透明背景。
- Given rail 采样区与 preview 背后的正文底色不同,When 上下文行显示,Then 行级 surface 自身提供足够对比度,当前项与邻近项不依赖宿主底色保持可读。
- Given 用户离开 rail 或 TOC 重绘,When 预览关闭,Then 旧的
.is-previewed状态被清理,不应残留高亮。 - Given 用户启用
prefers-reduced-motion: reduce,When 悬停 Barcode rail,Then wave 动画可以停用,但标题预览仍应显示。
边界情况:
- Hover wave 布局只缓存可视区域附近 item,并预计算基础宽度,避免长目录在 pointer move 中频繁全量读写布局。
- 预览气泡需限制在视口内,并与 hover 高亮条保持同一垂直中心。
.toc-rail-link允许 overflow visible,避免右侧 rail 向左延展时圆角端被父级裁切成平角。
功能描述:test-pages/rail-hover-performance.html 提供固定的 Rail QA 控制条,用于在本地页面内切换 Wheel / Spotlight / GPT、rail 位置、surface 和 reduced motion 状态,并同步到 URL 参数。
用户故事:作为维护扩展交互体验的开发者,我希望不用手动编辑 URL 就能快速切换 rail 验收场景,以便通过 Chrome/Computer 截图复核镜像方向、局部配色和减少动态效果。
验收标准:
- Given 打开
test-pages/rail-hover-performance.html?position=right&surface=dark,When 页面加载,ThenRail QA控制条中Right与Dark按钮应处于aria-pressed="true"状态。 - Given 用户点击
Wheel、Spotlight或GPT,When 页面重载,Then URL 的preview参数更新为wheel/spotlight/gpt,Barcode 标题预览同步切换。 - Given 用户点击
Left或Right,When 页面重载,Then URL 的position参数更新,rail 位置同步切换,控制条移动到 rail 的另一侧。 - Given 用户点击
Dark/Light/Color/Strip,When 页面重载,Then URL 的surface参数更新,页面背景和 rail 局部 surface 验收场景同步切换。 - Given 用户勾选
Reduce,When 页面重载,Then URL 添加motion=reduce,测试页 mock 的matchMedia('(prefers-reduced-motion: reduce)')返回 true。 - Given 视口宽度小于 760px,When 控制条内容换行,Then 正文顶部留出足够 padding,控制条不遮挡首屏标题。
功能描述:在 themePreset = 'barcode' 且 barcodePreview = 'spotlight' 时,idle 只显示透明 rail 与短横线;hover / focus 时显示当前可见的完整标题列,并将命中高亮向上下邻近项渐隐扩散。
用户故事:作为希望长期保持页面干净、但偶尔需要快速查看附近结构的读者,我希望标题只在主动探索 rail 时出现,而且能直接对应每条短横线。
验收标准:
- Given 指针不在 rail 或聚光灯标题上,When 页面处于 idle,Then
.toc-spotlight-row.is-visible数量为 0,页面只保留短横线。 - Given 用户 hover 中间 rail item,When 聚光灯层显示,Then 所有当前可见 bar 都有对应标题 row,DOM 不因命中项变化而重建。
- Given 标题列已显示,When 检查视觉层级,Then 命中项为 100%,上下距离 1 / 2 项约为 66% / 36%,其余可见标题约为 10%。
- Given 命中项显示,When 检查样式,Then
.toc-spotlight-text不出现 border,高亮仅使用亮度、轻微缩放、字重和 surface。 - Given TOC 位于右侧或左侧,When hover,Then 标题分别显示在 rail 左侧或右侧,布局镜像且不超出视口。
- Given 用户从 bar 移到聚光灯标题,When 点击标题,Then 原目录链接执行跳转、目标获得 active /
aria-current并保留短暂落点反馈。 - Given 用户通过键盘聚焦原 rail link,When focus 变化,Then同样显示该项附近上下文;原 link 仍提供可访问名称和导航语义。
- Given 用户启用 reduced motion,When hover 聚光灯,Then 标题仍显示,但聚光灯层 transition / animation 为 0。
性能边界:
- 聚光灯 rows 在当前 TOC 生命周期内一次建立并稳定复用;hover 命中只切换距离 class。
- pointer 热路径使用 rAF 合帧;wave 只读写可视区域附近 item,
will-change只在影响项启用。 - 主题与内容 MutationObserver 忽略聚光灯层内部更新。
功能描述:在 themePreset = 'barcode' 且 barcodePreview = 'gpt' 时,idle 只显示透明 rail 与短横线;hover / focus 后在 rail 内侧展开带背景、边框和圆角的完整标题面板。
用户故事:作为阅读超长文章的用户,我希望常驻状态尽量克制,但在需要时能打开一个可滚动的完整目录,并直接跳转到任意标题。
验收标准:
- Given 指针不在 rail 或 GPT 面板内,When 页面处于 idle,Then
.toc-gpt-preview不可见且页面只保留短横线。 - Given 用户 hover 任意 rail item,When GPT 面板展开,Then 面板包含当前 TOC 生命周期内的全部标题,命中行高亮并自动滚入面板视区。
- Given 标题数量超过面板高度,When 用户在
.toc-gpt-preview-list内滚动,Then 仅面板滚动,页面不跟随滚动。 - Given 用户从 rail 移向面板,When 指针跨越两者之间的短间隙,Then 96ms 离开宽限允许面板保持打开,进入面板后取消关闭。
- Given 用户点击面板标题,When 原
.toc-rail-link被触发,Then 页面平滑跳转并保持目标 active /aria-current反馈。 - Given GPT 面板包含大量标题,When 面板展开,Then 仅
.is-currentrow 使用tabIndex = 0,其余 rows 保持-1。 - Given 键盘焦点位于 GPT 当前 row,When 用户按 ArrowUp / ArrowDown / Home / End,Then 焦点与 current 状态移动到对应标题并自动进入面板视区。
- Given TOC 位于左侧或右侧,When 面板展开,Then 面板分别显示在 rail 右侧或左侧,并使用对应方向的 transform origin。
- Given 用户启用 reduced motion,When GPT 面板展开,Then 面板与内部 row 不执行 transition / animation,但滚动和跳转仍可用。
性能边界:
- 完整标题 button 在当前 TOC 生命周期内一次建立并复用;hover 命中只更新旧/新 current row、ARIA、roving tabindex 与必要的
scrollTop。 .toc-gpt-preview参与局部 surface token,但被主题与内容 MutationObserver 视为扩展自有 DOM。
功能描述:catalog.js 在 Barcode 下创建一个独立的回到顶部按钮(#github-sst)。它与 TOC rail 独立渲染,按统一的标题数/滚动屏数条件显示;标准目录面板使用面板内的 .toc-top-button。
用户故事:作为浏览长页面的用户,我希望在页面右下角看到一个回到顶部按钮,以便一键返回页面顶部,无需手动滚动。
验收标准:
- Given
themePreset = 'barcode'且页面满足显示条件,WhencreateUI()执行,Then 在document.body末尾创建 id 为github-sst、class 为github-sst的button元素。 - Given 页面不满足
minHeaders或showAfterScrollScreens条件,WhenupdateVisibility()执行,Then 按钮移除visibleclass、设置tabIndex = -1,并保持不可交互。 - Given 页面满足显示条件,When
updateVisibility()执行,Then 按钮获得visibleclass;指针接近时再通过is-near显形并允许点击。 - Given 按钮可见,When 用户点击按钮,Then 使用浏览器原生 smooth scroll 回到顶部;reduced motion 下使用即时滚动。
边界情况:
- 滚动监听通过
requestAnimationFrame节流(cancelAnimationFrame+ 新帧),防止高频回调。 - 滚动位置兼容写法:
document.body.scrollTop + document.documentElement.scrollTop。 - 回顶使用
window.scrollTo({ top: 0, left: 0, behavior }),不创建递归 timer。
功能描述:TOC 展开面板顶部标题行右侧有一个"Top"按钮,点击后平滑滚动回页面顶部。
用户故事:作为正在查看目录的用户,我希望不需要关闭目录就能直接点击"Top"按钮回到页面顶部,以便快捷操作。
验收标准:
- Given TOC 面板已展开,When 用户点击
.toc-top-button(文字"Top"),Then 调用scrollToTop(),页面以 348ms 动画平滑滚回顶部,默认浏览器行为被阻止。 - Given 在
press(长按)交互模式下,When 用户点击.toc-top-button,ThentocTree上的 click 事件不折叠面板(因为目标是.toc-top-button,被显式排除在折叠逻辑之外)。
功能描述:独立按钮(#github-sst)使用 18×18 的向上箭头 SVG 图标;TOC 图标使用 20×20 的三横线菜单 SVG 图标。
验收标准:
- Given 独立按钮已注入,When 检查 innerHTML,Then 包含
viewBox="0 0 24 24"的向上箭头路径 SVG(宽高各 18px)。 - Given TOC 容器已创建,When 检查 iconContainer,Then 包含
viewBox="0 0 24 24"的三横线菜单 SVG(宽高各 20px)。
功能描述:theme.js 直接读取页面背景亮度,不再扫描文本颜色。读取优先级为 body 背景色、html 背景色、color-scheme 属性/样式、prefers-color-scheme,最后以浅色背景作为兜底。
用户故事:作为在各种风格网站上使用扩展的用户,我希望 TOC 面板能自动匹配页面的整体色调,以便视觉上不显突兀。
验收标准:
- Given
body或html存在非透明背景色,WhengetPageBackgroundLightness()执行,Then 将 RGB 颜色转换为 HSL 并返回亮度 L。 - Given 页面背景色透明但
<html>带有深/浅色color-scheme,When 执行背景读取,Then 分别按深色/浅色返回低/高亮度兜底值。 - Given 以上信息均不可用,When
selectTheme()执行,Then 按浅色背景兜底并返回theme-dark。
功能描述:将实际读取到的 RGB 背景色转换为 HSL,只使用亮度(L)决定浮层的明暗方向。
验收标准:
- Given RGB 字符串格式,When 调用
rgbToHsl,Then 返回包含h(0–360)、s(0–100)、l(0–100)的对象。 - Given RGB 字符串无法解析(match 返回 null),When 调用
rgbToHsl,Then 返回null,背景读取继续尝试下一个来源。
功能描述:根据页面背景亮度在 theme-dark 与 theme-light 之间选择浮层主题;另有 theme-auto 和旧彩色 class 作为 CSS 兼容类,但不会被 selectTheme() 主动选中。
用户故事:作为用户,我希望扩展在深色网站上显示浅色面板、在浅色网站上显示深色面板,以保证 TOC 始终清晰可见。
验收标准:
- Given 页面背景亮度
L > 60,WhenselectTheme()执行,Then 应用theme-dark,使用深色浮层适配浅色页面。 - Given 页面背景亮度
L <= 60,WhenselectTheme()执行,Then 应用theme-light,使用浅色浮层适配深色/中色页面。
重要实现细节:主题 class 会应用到 #github-toc 和 #github-sst;应用新主题前会移除 theme-dark、theme-light、theme-blue、theme-green、theme-purple、theme-auto 以及 preset class,再添加当前主题与 theme-preset-default/sspai。
功能描述:theme.js 通过 MutationObserver 监听 body 的 childList/subtree,以及 body/html 的 class/style 属性变化,在页面内容或页面主题变化时重新检测并应用主题。
验收标准:
- Given 页面通过 JavaScript 动态替换了主要内容区域(如 SPA 路由切换),When DOM 发生变化,Then
applyTheme重新执行,TOC 主题随之更新。 - Given 扩展注入完成,When 页面初始加载,Then
applyTheme立即调用一次(初始应用)。
功能描述:基础主题通过 CSS 自定义属性定义背景、文字、高亮、滚动条和阴影;导航 preset 再覆盖 rail 的透明背景、局部 surface、预览层和回顶按钮 token。
| class / preset | 用途 | |---|---|---| | theme-light | 深色/中色页面使用的浅色磨砂浮层 | | theme-dark | 浅色页面使用的深色磨砂浮层 | | theme-auto | 跟随系统偏好的兼容 class | | theme-blue / theme-green / theme-purple | 映射到深色变量的旧 class,保留向后兼容 | | theme-preset-default | 标准目录面板 preset | | theme-preset-sspai | 透明阅读进度 rail、局部自适应和外置标题预览 preset |
验收标准:
- Given 任意基础主题 class 应用于
#github-toc,When 检查 CSS 变量,Then 背景、文字、滚动条和阴影 token 均有可用定义。 - Given 主题应用,When TOC 展开,Then 阴影升级为
--toc-elevation-expanded(elevation-3:最高层级)。
功能描述:鼠标悬停在 TOC 容器上时自动展开面板,移开鼠标后自动折叠;点击图标可将当前展开状态固定/解除固定,不直接回顶。
用户故事:作为桌面端用户,我希望鼠标悬停就能自动展开目录,无需多余点击,以便快速浏览章节列表。
验收标准:
- Given
expandMode = 'hover',When 鼠标 mouseenter 至.github-toc,ThentoggleExpanded(true)被调用,容器获得expandedclass,iconContainer opacity 设为 0。 - Given
expandMode = 'hover',When 鼠标 mouseleave 离开.github-toc,ThentoggleExpanded(false)被调用,容器移除expandedclass,iconContainer opacity 恢复为 1。 - Given
expandMode = 'hover',When 用户点击.toc-icon,Then 打开并固定面板,或关闭已固定的面板;不直接调用scrollToTop()。 - Given
expandMode = 'hover',When 面板处于展开状态,点击.toc-tree中非链接、非.toc-top-button区域,Then 调用toggleExpanded(false),面板折叠。
功能描述:点击 TOC 容器非链接区域展开/折叠面板;点击图标遵循同一切换逻辑,不直接回顶。
用户故事:作为触控板用户或偏好精确控制的桌面用户,我希望手动点击才展开目录,避免误触悬停自动展开。
验收标准:
- Given
expandMode = 'click',When 用户点击.github-toc非链接区域,Then 调用toggleExpanded()(切换),若已展开则折叠,若已折叠则展开。 - Given
expandMode = 'click',面板未展开,When 用户点击.toc-icon区域,Then 面板固定展开。 - Given
expandMode = 'click',When 用户点击.toc-item a链接,Then click 事件不触发展开/折叠(isClickOnTocLink返回 true,直接 return)。 - Given
expandMode = 'click',面板已展开,When 用户点击.toc-tree中非链接、非.toc-top-button区域,Then 面板折叠。
功能描述:长按(pointerdown 持续 450ms)触发展开/折叠;短按(快速释放)触发回到顶部;离开目标区域取消计时器。
用户故事:作为移动端或触摸屏用户,我希望通过长按手势展开目录,通过短按快速回到顶部,以便在触摸设备上高效操作。
验收标准:
- Given
expandMode = 'press',When 用户 pointerdown 并持续超过 450ms,ThenlongPressTriggered = true,调用toggleExpanded()(切换展开状态)。 - Given
expandMode = 'press',When 用户 pointerdown 后在 450ms 内 pointerup(短按),ThenlongPressTriggered仍为 false,调用scrollToTop()(回到顶部)。 - Given
expandMode = 'press',面板已展开,When 短按.toc-icon区域(pointerup),Then 调用toggleExpanded(false)折叠面板,不触发 scrollToTop。 - Given 用户 pointerdown 后手指/指针移出容器(pointerleave),When pointerleave 触发,Then 长按计时器被清除,不触发 toggleExpanded。
- Given
expandMode = 'press',When 用户点击.toc-item a链接,ThenisClickOnTocLink检查通过,pointerdown 不启动计时器(直接 return)。
重要状态变量:longPressTimer(setTimeout 引用)、longPressTriggered(Boolean,每次 pointerdown 时重置为 false)。
功能描述:当页面上任意 INPUT 或 TEXTAREA 获得焦点时,TOC 面板自动折叠,避免遮挡输入内容。
用户故事:作为在包含搜索框或表单的页面上使用扩展的用户,我希望在点击输入框时 TOC 自动收起,以免遮挡我的输入区域。
验收标准:
- Given TOC 面板当前处于展开状态,When 页面上任意
INPUT或TEXTAREA元素触发focusin事件,ThentoggleExpanded(false)被调用,面板折叠。 - Given 焦点进入其他非输入类元素(如 div、button),When focusin 触发,Then 面板不自动折叠。
功能描述:统一的展开/折叠控制函数,支持强制指定状态或切换当前状态。
验收标准:
- Given 调用
toggleExpanded(true),When 执行,Then.github-toc获得expandedclass,aria-expanded变为true,.toc-tree[aria-hidden]变为false。 - Given 调用
toggleExpanded(false),When 执行,Then.github-toc移除expandedclass,aria-expanded变为false,.toc-tree[aria-hidden]变为true。 - Given 调用
toggleExpanded()(无参数),When 执行,Then 检测当前是否含expandedclass,进行切换。 - Given
tocContainer为 null(UI 尚未创建),When 调用toggleExpanded,Then 立即 return,不抛出异常。
功能描述:Options 页面使用紧凑设置行、分段按钮、开关胶囊和固定底部保存区组织配置。面板由 header、独立滚动的 settings list、footer 三段组成,保存区从首屏开始持续可见。
用户故事:作为调整阅读导航偏好的用户,我希望能快速理解每个设置项,并在滚动到页面任意位置时都能清楚看到保存入口。
验收标准:
- Given 用户在普通桌面 Chrome 窗口打开 Options 页面,When 页面处于暗色系统配色,Then 选中的分段按钮应使用高对比主行动色,不应出现过亮白块抢占整页焦点。
- Given 用户刚打开设置页或滚动设置列表,When 任意配置行位于视口中,Then 保存按钮与“偏好只保存在浏览器中”的说明始终可见。
- Given 辅助技术读取“显示条件”数值输入,When 聚焦滚动屏数或最少标题输入框,Then 控件应分别暴露“滚动屏数”和“最少标题”的可理解名称。
- Given 用户正在浏览设置项,When 阅读每行说明,Then 文案应保持短句,优先解释用户结果而不是实现细节。
功能描述:扩展设置页面包含导航类型、Barcode 标题预览、标准面板交互方式、显示条件、位置、兼容策略、禁用域名,以及底部保存按钮和状态提示;两个二级设置行按一级类型条件显示。
用户故事:作为扩展用户,我希望通过一个清晰分组的设置页面管理所有配置项,以便找到并修改我需要的选项。
验收标准:
- Given 用户打开扩展设置页,When 页面加载,Then 根据一级导航类型展示六个相关设置行;Barcode 预览与标准面板交互方式不会同时出现。
- Given 设置页面加载,When
loadSettings()返回数据,Then 所有表单字段(themePreset、barcodePreview、expandMode、minHeaders、showAfterScrollScreens、position、disabledDomains、avoidExistingWidgets、forceShow)填充已保存的值。 - Given 旧设置为
sspai/glimmer,When 设置加载,Then 自动迁移为barcode + wheel/barcode + spotlight。 - Given
chrome.storage.sync不可用(非扩展环境),When 设置页面加载,Then 使用默认值填充表单,不抛出异常。
功能描述:标准目录面板以分段按钮呈现悬停展开(hover,默认)、长按展开(press)、点击展开(click);Barcode 下隐藏该行并固定使用 rail hover 交互。
验收标准:
- Given 页面加载完成,When 已保存值为
hover/press/click之一,Then 下拉框选中对应选项。 - Given 用户保存非法值(非
hover/press/click),WhennormalizeSettings执行,Then 重置为默认值hover。
功能描述:数字输入框,最小值 0,步长 0.5,控制 TOC 面板在滚动多少屏后才显示。
验收标准:
- Given 用户输入
1.5,When 保存,Then 存储值为数字1.5,TOC 在scrollTop > window.innerHeight * 1.5时显示。 - Given 用户输入负数,When
normalizeSettings执行,Then 值被Math.max(0, value)截断为 0。 - Given 输入非有限数(NaN、Infinity),When
normalizeSettings执行,Then 重置为默认值1。
功能描述:数字输入框,最小值 0,步长 1,控制页面至少需要多少个标题才显示 TOC。
验收标准:
- Given 用户设置
minHeaders = 5,当前页面只有 3 个标题,WhenshouldShowToc()执行,Then 返回 false,TOC 不显示。 - Given 用户设置
minHeaders = 0,When 页面有 1 个标题,Then 标题数量条件满足(1 >= 0)。 - Given 输入负数,When
normalizeSettings执行,Then 截断为 0。 - Given 输入非有限数,When
normalizeSettings执行,Then 重置为默认值3。
功能描述:原生 select 作为可访问数据源,界面以分段按钮呈现两个选项:右下角(right,默认)、左下角(left),控制 TOC 容器的固定定位位置。
验收标准:
- Given
position = 'right',When UI 创建,Then.github-toc同时具有position-rightclass(right: 20px; left: auto)。 - Given
position = 'left',When UI 创建,Then.github-toc具有position-leftclass(left: 20px; right: auto)。 - Given 保存非法 position 值(非
left/right),WhennormalizeSettings执行,Then 重置为默认值right。
功能描述:多行文本输入框,接受逗号分隔的域名列表;内容会被解析为字符串数组(去除空白、过滤空项)并存储。
验收标准:
- Given 用户输入
"example.com, docs.example.org",When 保存,ThendisabledDomains存储为["example.com", "docs.example.org"]。 - Given 用户输入含多余空格或空条目(如
" , example.com, , "),WhennormalizeDomains执行,Then 结果为["example.com"](trim + filter 空字符串)。 - Given
disabledDomains从存储中读取为非数组类型,WhennormalizeSettings执行,Then 重置为空数组[]。 - Given 加载设置时域名数组存在,When
bindForm执行,Then textarea 内容为数组以", "分隔拼接的字符串。
功能描述:复选框,默认勾选;当页面已存在 TOC 或回到顶部按钮时,扩展自动跳过注入。
验收标准:
- Given
avoidExistingWidgets = true且forceShow = false,页面已有符合条件的 TOC 或回到顶部按钮,WhengetSkipInjectionDecision()执行,Then 返回带类型/来源的跳过决策,start()函数直接 return,不注入任何 UI。 - Given
avoidExistingWidgets = false,WhengetSkipInjectionDecision()执行,Then 发布“由设置禁用检测”的诊断快照并返回 null。 - Given
forceShow = true,WhengetSkipInjectionDecision()执行,Then 直接返回 null(forceShow优先级高于avoidExistingWidgets)。
功能描述:复选框,默认不勾选;勾选后即使页面已有 TOC/回到顶部按钮也强制注入扩展 UI。
用户故事:作为在有内置 TOC 的文档网站上偏好使用扩展自有面板的用户,我希望能强制显示扩展 TOC,以便使用统一的交互体验。
验收标准:
- Given
forceShow复选框被勾选,WhensyncForceShow()执行,ThenavoidExistingWidgets复选框被强制取消勾选并disabled = true。 - Given
forceShow复选框被取消勾选,WhensyncForceShow()执行,ThenavoidExistingWidgets复选框重新启用(disabled = false)。 - Given
forceShow = true,avoidExistingWidgets = true,WhengetSkipInjectionDecision()执行,Then 因为forceShow优先级最高直接返回 null,照常注入 UI。
注意:forceShow 与 avoidExistingWidgets 为互斥配置,设置页面在 UI 层强制互斥,但存储层两者独立保存。
功能描述:页面加载后保存按钮显示“已保存”并禁用;任意配置改变后按钮启用并显示“保存更改”。点击后将 9 个字段写入 chrome.storage.sync,按钮恢复 clean state,并在旁边显示“设置已保存”提示。
验收标准:
- Given 用户修改任意设置项,When change / input 事件触发,Then按钮启用并显示“保存更改”。
- Given 用户点击“保存更改”,Then
chrome.storage.sync.set被调用,写入所有 9 个字段的最新值。 - Given 保存操作完成,When
renderStatus('设置已保存')被调用,Then按钮恢复禁用的“已保存”,#status显示“设置已保存”并在 1500ms 后淡出。 - Given
chrome.storage.sync不可用,When 点击保存,ThensaveSettings直接 resolve,不抛出异常,状态提示仍然显示。
所有配置项的默认值(用于 normalizeSettings 的 fallback 和 storage 初始读取):
| 配置项 | 默认值 | 类型 |
|---|---|---|
| themePreset | 'default' |
'default' | 'barcode' |
| barcodePreview | 'wheel' |
'wheel' | 'spotlight' | 'gpt' |
| expandMode | 'hover' |
string |
| minHeaders | 3 |
number |
| showAfterScrollScreens | 1 |
number |
| position | 'right' |
string |
| disabledDomains | [] |
string[] |
| avoidExistingWidgets | true |
boolean |
| forceShow | false |
boolean |
功能描述:重写 history.pushState 和 history.replaceState,在路由切换后 120ms 延迟重新初始化 TOC。
用户故事:作为使用 React Router、Vue Router 等前端路由框架的 SPA 用户,我希望导航到新页面后 TOC 能自动更新,以便始终反映当前页面的章节结构。
验收标准:
- Given 应用调用
history.pushState,When 调用后,Then 原始pushState正常执行,同时 120ms 后触发reinitializeTOC()。 - Given 应用调用
history.replaceState,When 调用后,Then 原始replaceState正常执行,同时 120ms 后触发reinitializeTOC()。 - Given 用户点击浏览器后退/前进按钮,When
popstate事件触发,Then 120ms 后触发reinitializeTOC()。
功能描述:在 github.com 域名下额外监听 pjax:start、pjax:end、turbo:load、ajaxComplete 事件,以应对 GitHub 的 Turbo/Pjax 导航机制。
用户故事:作为 GitHub 用户,我希望在点击仓库文件树、切换分支等 GitHub 内部导航操作后,TOC 能自动更新为新页面的章节,以便在 GitHub 上流畅使用扩展。
验收标准:
- Given 当前域名为
github.com,Whenpjax:start事件触发,Then 调用cleanup()(断开 MutationObserver,清除 timeout,清空 lastProcessedHeaders)。 - Given 当前域名为
github.com,Whenpjax:end事件触发,Then 120ms 后触发reinitializeTOC()。 - Given 当前域名为
github.com,Whenturbo:load事件触发,Then 120ms 后触发reinitializeTOC()。 - Given 当前域名为
github.com,WhenajaxComplete事件触发,Then 120ms 后触发reinitializeTOC()。 - Given 当前域名不是
github.com,WhensetupGitHubListener()调用,Then 上述事件监听器一律不添加。
功能描述:监听 Astro 页面交换/加载、pageshow 和页面重新变为可见等生命周期事件,在客户端页面恢复或 BFCache 返回后重新初始化 TOC。
验收标准:
- Given
astro:after-swap或astro:page-load事件触发,Then 延迟 80ms 调用reinitializeTOC()。 - Given
pageshow事件触发,Then 延迟 80ms 调用reinitializeTOC()。 - Given
visibilitychange后页面变为 visible,Then 延迟 100ms 调用reinitializeTOC()。 - 当前实现不使用 GitHub 每秒轮询;内容变化主要由事件监听和标题节点 MutationObserver 覆盖。
功能描述:监听当前内容容器(尚未确定时回退到 document.body)的 childList/subtree 变化,并结合 URL 变化检测标题节点是否受影响;命中后经过防抖和延迟重新初始化 TOC。
验收标准:
- Given MutationObserver 已挂载,When 新增/移除节点命中 H1–H6 或自定义标题选择器,Then 经过 250ms 防抖后,再延迟 120ms 触发
reinitializeTOC()。 - Given MutationObserver 已挂载,When 检测到 URL 发生变化(
checkUrlChange()返回 true),Then 触发reinitializeTOC()。 - Given
reinitializeTOC()调用,When 内容容器未变化且 observer 仍存在,Then 只更新 TOC;容器变化或 observer 不存在时,setupObserver()会先断开旧 observer 再重新挂载。
边界情况:
debounce延迟为 250ms,防止页面频繁 DOM 更新导致 TOC 反复重建。- 每次
updateTOC前清空lastProcessedHeadersSet,确保重新扫描所有标题。
功能描述:脚本执行时首先检查 #github-toc 是否已存在,已存在则立即退出整个 IIFE,避免重复注入。
验收标准:
- Given
#github-toc已存在于 DOM 中,Whencatalog.js再次执行,Then 立即 return,不创建新的 TOC 容器,不重复绑定事件。 - Given历史遗留的
#github-sst已存在于 DOM 中,Whenstart()执行,Then 先移除旧节点,再按当前 preset 重新创建所需 UI,避免旧实现残留。
功能描述:使用 measurePerformance 函数包裹三类关键操作:TOC 生成(tocGeneration)、TOC 更新(tocUpdate)、滚动性能(scrollPerformance),记录每次操作的耗时(ms)与内存变化(bytes)。
用户故事:作为扩展开发者,我希望能在页面上实时查看 TOC 各操作的性能统计,以便识别性能瓶颈并做针对性优化。
验收标准:
- Given
measurePerformance('tocUpdate', callback)被调用,When callback 执行,Then 记录{timestamp, duration, memoryDelta, memoryUsage}到performanceMetrics.tocUpdate数组。 - Given
performance.memory不可用(非 Chromium 环境),WhenmeasurePerformance执行,ThenstartMemory和endMemory均为 0,memoryDelta = 0,不抛出异常。 - Given 某个指标数组长度超过 100 条,When 新记录写入,Then 移除最老的一条(
shift()),保持最多 100 条。
功能描述:在页面左下角注入一个隐藏的性能统计浮层(#toc-performance-stats),通过快捷键 Ctrl+Shift+P 切换显示/隐藏,每秒刷新一次统计数据。
验收标准:
- Given 扩展初始化完成,When
setupPerformanceStats()注册快捷键,Then 首次调用快捷键时由ensurePerformanceStatsContainer()将#toc-performance-stats添加到document.body,初始display: none。 - Given 性能面板已注入,When 用户按下 Ctrl+Shift+P,Then 面板在
display: none和display: block之间切换。 - Given 面板处于
display: block,When 每秒定时刷新,Then 面板 innerHTML 更新,显示 Generation、Updates、Scroll Performance 三组统计(count、avgDuration、minDuration、maxDuration、memoryUsage/memoryDelta),以 MB 为单位格式化内存,以 ms 为单位格式化时间(保留 2 位小数)。 - Given 所有指标均无数据(全为 null),When 定时刷新,Then 面板内容不更新(
if (!stats.generation && !stats.update && !stats.scroll) return)。
功能描述:getPerformanceStats 根据历史记录计算 count、平均耗时、最短/最长耗时、平均内存变化、最新内存占用。
验收标准:
- Given 某指标数组为空,When
getPerformanceStats被调用,Then 返回null。 - Given 某指标数组有 N 条记录,When
getPerformanceStats被调用,Then 返回包含 count=N、avgDuration(所有 duration 均值)、minDuration(最小值)、maxDuration(最大值)、avgMemoryDelta(均值)、latestMemoryUsage(最后一条)的对象。
功能描述:未展开时,TOC 容器呈直径 44px 的圆形浮标,固定在屏幕角落,不透明度 0.38,z-index 9999。
验收标准:
- Given TOC 容器已创建,When 未展开,Then 元素为
position: fixed,width: 44px,height: 44px,border-radius: 50%,opacity: 0.38,z-index: 9999。 - Given 用户鼠标悬停在圆形浮标上(CSS :hover),When hover 状态,Then
opacity升至 0.76,box-shadow升级为--toc-elevation-hover。 - Given 容器为
position-right,When 渲染,Thenright: 24px; left: auto(距右边 24px、距底部 24px)。 - Given 容器为
position-left,When 渲染,Thenleft: 24px; right: auto(距左边 24px、距底部 24px)。
功能描述:TOC 容器从圆形扩展为宽 280px、高度不超过 min(420px, calc(100vh - 64px)) 的矩形卡片,使用展开/收缩专用缓动,过渡时长约 0.3s。
验收标准:
- Given
.github-toc获得expandedclass,When CSS transition 执行,Then 容器从 44×44px 过渡到宽 280px、最大高 420px,border-radius 从 50% 过渡到 12px,opacity 升至 1,box-shadow 升级为--toc-elevation-expanded。 - Given
.github-toc失去expandedclass,When CSS transition 执行,Then 容器反向收缩回 44×44px,圆角、透明度同步复原。 - Given
will-change: transform, width, height, border-radius已声明,When 动画执行,Then GPU 加速层提前创建,避免合成层抖动。
功能描述:展开时菜单图标淡出缩小(opacity: 0,scale: 0.8);折叠时图标淡入恢复。
验收标准:
- Given
.github-toc.expanded svg,When expanded class 存在,Then SVGopacity: 0,并在图标容器已有居中 transform 的基础上缩放为scale(0.8)。 - Given expanded class 移除,When transition 执行,Then SVG 恢复 opacity 和 scale(通过
.toc-icon的transition: all 0.3s var(--ease-out)驱动)。
功能描述:展开时,toc-tree 从 scale(0.96) 淡入,标题条目以最多约 200ms 的阶梯延迟从 translateY(4px) 滑入。
验收标准:
- Given
.github-toc.expanded .toc-tree,When expanded,Then toc-treeopacity: 1,transform: scale(1)(从 scale(0.96) 过渡);pointer-events 从 none 变为 auto。 - Given
.github-toc.expanded .toc-title,When expanded,Thenopacity: 1,transform: translateY(0)(从 translateY(-10px) 过渡)。 - Given
.github-toc.expanded .toc-item,When expanded,Thenopacity: 1,transform: translateY(0)(从 translateY(4px) 过渡)。 - Given 前 8 个 toc-item,When expanded,Then 延迟从 0.03s 递增至 0.20s;第 9 项及以后保持 0.20s 封顶。
功能描述:toc-list 区域使用 ::-webkit-scrollbar 定制 4px 宽、圆角 2px 的滚动条,颜色跟随主题 CSS 变量。
验收标准:
- Given toc-list 内容超出可用高度,When 出现滚动条,Then 滚动条宽度 4px,轨道透明,滑块颜色为
--toc-scrollbar-thumb,圆角为 2px。
功能描述:TOC 容器内所有元素强制使用 Roboto/Segoe UI/Arial 字体栈,通过 !important 防止宿主页面 CSS 污染;原生 .toc-icon 按钮通过 all: initial 重置所有继承样式。
验收标准:
- Given 宿主页面为任意样式的网站,When TOC 渲染,Then
.github-toc, .github-toc *字体强制为'Roboto', 'Segoe UI', 'Arial', 'Helvetica Neue', Arial, sans-serif。 - Given 宿主页面对
button有全局样式,When TOC icon 渲染,Then.github-toc .toc-icon通过all: initial重置,并重新声明 appearance、位置、尺寸、z-index 等必要属性。 - Given 宿主页面对 SVG 有 fill/color 等样式,When TOC 图标渲染,Then SVG 的 fill/color/stroke 均通过
!important强制为var(--toc-text),background/box-shadow/border/outline 强制为 none。 - Given 图标容器
.toc-icon::before和.toc-icon::after伪元素,When 宿主页面有全局伪元素样式,Then 两者被强制display: none !important; content: none !important。
功能描述:目录链接文字超出行宽时自动截断并显示省略号。
验收标准:
- Given 标题文字较长超出 toc-item 宽度,When 渲染,Then
white-space: nowrap、overflow: hidden、text-overflow: ellipsis共同作用,文字末尾显示...。
功能描述::root 定义三个标准 Material Design 阴影变量(elevation-1/2/3),供各主题的 toc-elevation 系列变量引用。
验收标准:
- Given
:rootCSS,Then--elevation-1、--elevation-2、--elevation-3均已定义,遵循 Material Design 阴影规范(多层 rgba box-shadow)。 - Given
--ease-out: cubic-bezier(0.0, 0.0, 0.2, 1)和--ease-in: cubic-bezier(0.4, 0.0, 1, 1)均已定义。
功能描述:findContentContainer() 按三个层次逐步回退查找最合适的内容容器。
用户故事:作为访问各类网站(博客、文档、GitHub、技术文章)的用户,我希望扩展能准确识别页面的主要内容区域,以便只抓取正文标题而非导航菜单标题。
验收标准:
- Given 页面存在
mainContainers列表中的任意选择器匹配元素(按列表顺序优先),WhenfindContentContainer()执行,Then 返回第一个匹配到的元素。 - Given 前述列表无匹配,When 执行,Then 在
article, main, [role="main"], [role="article"], [role="document"]中选文本内容最长的元素(textContent.length 最大值)。 - Given 上述两步均无结果,When 执行,Then 扫描所有
div, section, article, main元素,选包含 H 标签数量最多的元素作为容器。 - Given 三个层次均无合适结果,When 执行,Then 返回
document.body作为兜底容器。
mainContainers 选择器完整列表(共 36 项,顺序即优先级):
main-container, body-container, application-main, main-content, content, article, main, .markdown-body, #readme, .repository-content, .js-repo-root, .documentation, .docs-content, .doc-content, .doc-body, .document-body, .article-content, .post-content, .entry-content, .blog-post, .post-body, .article-body, .entry-body, .technical-docs, .api-docs, .guide-content, .tutorial-content, .mdx-content, .md-content, .rst-content, .asciidoc-content, [role="main"], [role="article"], [role="document"], [itemprop="articleBody"], [itemprop="mainContentOfPage"]
功能描述:标题提取时递归检查每个标题的祖先元素,若祖先匹配 excludeContainers 列表中任意选择器,则排除该标题。
验收标准:
- Given 标题在
nav、header、footer、.sidebar、.toc等导航/侧边栏容器内,WhengetHeaders()执行,Then 该标题被过滤,不出现在目录中。 - Given 标题在
[role="navigation"]、[role="banner"]、[role="contentinfo"]、[role="complementary"]等语义角色容器内,WhengetHeaders()执行,Then 该标题被过滤。 - Given 标题在 GitHub 特定容器(
.js-header-wrapper、.js-repo-nav、.js-site-header、.js-site-footer、.js-notification-shelf)内,WhengetHeaders()执行,Then 该标题被过滤。 - Given 标题的祖先链中无任何 excludeContainers 匹配项,When
getHeaders()执行,Then 标题正常纳入目录。
功能描述:检测页面是否已存在符合条件的侧边栏 TOC,判断标准为:元素可见(width > 40 && height > 40)、包含 3+ 个锚链接、位于页面边缘或固定/sticky 定位的侧边栏中。
验收标准:
- Given 页面有
#toc、#table-of-contents、.toc、.toc-container等匹配元素,WhengetExistingTocDecision()执行,Then 检查候选元素是否满足可见、包含真实锚点、位于侧栏/边缘 rail 等条件,并返回带来源的决策对象。 - Given 候选元素
display: none或尺寸 <= 40px,WhenisVisible()执行,Then 返回 false,该元素不计入"已有 TOC"。 - Given 候选元素包含的同页链接少于 3 个,When
hasTocLinks()执行,Then 返回 false;同页链接可以是 hash 或解析后仍指向当前 pathname 的 URL。 - Given 候选元素的同页链接全部没有实际 hash 目标,When
hasTocLinks()执行,Then 返回 false(要求至少一个解析后的 hash 长度大于 1)。 - Given 候选元素
left < viewportWidth * 35%或right > viewportWidth * 65%(位于边缘),且 position 为 fixed/sticky 或位于aside, nav, .sidebar等侧边栏容器内,WhenisLikelySidebarToc()执行,Then 返回 true。
功能描述:检测页面是否已存在可见且固定/sticky 定位的回到顶部按钮;可见性判断要求宽高均大于 40px。
验收标准:
- Given 页面有
#scroll-to-top、#back-to-top、.scroll-to-top、.back-to-top、.scrolltop、.to-top、[data-scroll-to-top]、[aria-label*="scroll to top" i]匹配元素,WhengetExistingScrollToTopDecision()执行,Then 检查元素是否满足isVisible + isFixedOrSticky,并返回带来源的决策对象。 - Given 候选元素
position: fixed或position: sticky,且通过isVisible()尺寸检查,WhengetExistingScrollToTopDecision()执行,Then 返回回顶控件决策。 - Given 候选元素
position: relative(非固定),WhenisFixedOrSticky()执行,Then 返回 false。
功能描述:从 chrome.storage.sync 加载设置后,经 normalizeSettings 校验 preset/交互/位置枚举、数值范围和域名数组,防止主要非法配置导致运行时错误。
验收标准:
- Given
chrome.storage.sync不可用,WhenloadSettings()执行,Then resolve 默认设置对象,不抛出异常。 - Given 设置加载失败(Promise rejected),When
start()调用链中的 catch 执行,Then 使用默认设置{ ...defaultSettings }继续执行start()。 - Given 所有已校验字段都有效,When
normalizeSettings执行,Then 各字段值保持不变,仅补全缺失字段为默认值;avoidExistingWidgets与forceShow由设置页联动保持互斥,但函数本身不强制转换其布尔类型。
功能描述:isDomainDisabled() 检查当前页面 hostname 是否在 disabledDomains 数组中,若匹配则 start() 立即退出,不注入任何 UI。
用户故事:作为在某些特定网站上不需要 TOC 功能的用户,我希望能将这些网站加入黑名单,以便在这些站点上扩展完全静默。
验收标准:
- Given
disabledDomains = ["example.com"],当前hostname = "example.com",WhenisDomainDisabled()执行,Then 返回 true,start()函数立即 return,不创建任何 DOM 元素,不绑定任何事件。 - Given
disabledDomains = ["example.com"],当前hostname = "sub.example.com",WhenisDomainDisabled()执行,Then 返回 false(精确匹配,子域名不匹配父域名)。 - Given
disabledDomains = []或undefined,WhenisDomainDisabled()执行,Then 返回 false,不影响正常运行。 - Given
disabledDomains为非数组类型,WhennormalizeSettings执行,Then 重置为[],isDomainDisabled()返回 false。
功能描述:manifest 声明的权限决定扩展可访问的能力边界。
验收标准:
- Given manifest
"permissions": ["activeTab", "storage"],When 扩展运行,Then 可调用chrome.storage.sync(设置读写),不需要额外权限声明。 - Given manifest
"host_permissions": ["<all_urls>"],When 扩展运行,Then content scripts 注入到所有 http/https 页面,包括非 GitHub 网站。 - Given manifest
"content_scripts": [{matches: ["<all_urls>"], js: ["catalog.js", "theme.js"], css: ["toc.css", "themes.css"]}],When 页面加载,Thencatalog.js和theme.js按序注入,toc.css和themes.css同步注入为页面样式,且独立滚动按钮由catalog.js创建。
功能描述:catalog.js 与 theme.js 均包裹在立即执行函数表达式(IIFE)中,防止全局变量污染宿主页面。
验收标准:
- Given 扩展注入完成,When 在宿主页面全局
window对象上检查,Then 不存在tocContainer、iconContainer、settings等扩展内部变量。 - Given 多个 content script 同时运行(catalog.js 和 theme.js),When 各自执行,Then 互相不污染对方的局部变量。
| 函数名 | 文件 | 职责 |
|---|---|---|
start() |
catalog.js | 总入口:检查禁用域名 → 检查已有控件 → 创建 UI → 绑定交互 → 初始化 |
createUI() |
catalog.js | 创建 #github-toc 容器、图标、toc-tree、toc-title、toc-top-button、toc-list |
initialize() |
catalog.js | 查找容器 → 启动 MutationObserver → 绑定 History/GitHub 监听 → 注入性能面板 |
updateTOC() |
catalog.js | 清理 → 获取标题 → 渲染条目 → 更新高亮 → 更新可见性;被 measurePerformance('tocUpdate') 包裹 |
reinitializeTOC() |
catalog.js | cleanup → findContentContainer → setupObserver → updateTOC;被 measurePerformance('tocGeneration') 包裹 |
getHeaders() |
catalog.js | 合并标准+自定义标题 → 过滤不可见/排除容器/重复项 → 按文档顺序排序 |
findContentContainer() |
catalog.js | 三层回退策略查找最优内容容器 |
updateActiveHeader() |
catalog.js | 根据滚动位置找最近标题(top <= 100)并更新 active class |
shouldShowToc() |
catalog.js | 判断是否满足显示条件(headerCount >= minHeaders && scrollTop > threshold) |
setupInteractions() |
catalog.js | 根据 expandMode 绑定 hover/click/press 三种交互逻辑 |
toggleExpanded(force?) |
catalog.js | 核心展开/折叠逻辑 |
scrollToTop() |
catalog.js | 选择正确的滚动容器并调用自定义 scrollTo 动画 |
scrollTo(el, to, duration) |
catalog.js | 递归定时滚动动画,每 10ms 步进,duration <= 0 时终止 |
createSspaiUI() |
catalog.js | 创建阅读进度 rail、body-level 标题预览和独立回顶按钮 #github-sst |
setupObserver() |
catalog.js | 创建/重建 MutationObserver,debounce 250ms |
setupHistoryListener() |
catalog.js | 拦截 pushState/replaceState + popstate |
setupGitHubListener() |
catalog.js | GitHub pjax/turbo/ajax 事件监听(仅 github.com) |
normalizeSettings(input) |
catalog.js | 校验并修正所有配置字段 |
loadSettings() |
catalog.js / options.js | 从 chrome.storage.sync 读取设置,不可用时 resolve 默认值 |
isDomainDisabled() |
catalog.js | 精确匹配 hostname 是否在黑名单中 |
getSkipInjectionDecision() |
catalog.js | 综合 forceShow、avoidExistingWidgets 与已有控件检测,返回跳过决策或 null |
getExistingTocDecision() |
catalog.js | 检测页面已有侧边栏 TOC 并记录来源 |
getExistingScrollToTopDecision() |
catalog.js | 检测页面已有固定回到顶部按钮并记录来源 |
measurePerformance(name, cb) |
catalog.js | 性能采集包装器 |
ensurePerformanceStatsContainer() |
catalog.js | 按需创建隐藏的性能面板容器 |
setupPerformanceStats() |
catalog.js | 绑定 Ctrl+Shift+P 性能面板快捷键 |
selectTheme() |
theme.js | 根据页面背景亮度选择 theme-light 或 theme-dark |
applyTheme() |
theme.js | 移除旧主题/preset class,并应用主题到 #github-toc 与 #github-sst |
bindForm(settings) |
options.js | 将设置对象填充到设置页面所有表单字段 |
syncForceShow() |
options.js | forceShow 勾选时禁用并取消 avoidExistingWidgets |
normalizeDomains(input) |
options.js | 解析逗号分隔域名字符串为数组 |
| 配置项 | 默认值 | 合法值 | 非法时行为 |
|---|---|---|---|
themePreset |
'default' |
'default' | 'barcode' |
重置为 'default';旧 sspai/glimmer 自动迁移 |
barcodePreview |
'wheel' |
'wheel' | 'spotlight' | 'gpt' |
重置为 'wheel' |
expandMode |
'hover' |
'hover' | 'press' | 'click' |
重置为 'hover' |
minHeaders |
3 |
有限正数或 0 | 非有限数重置为 3;负数截断为 0 |
showAfterScrollScreens |
1 |
有限正数或 0 | 非有限数重置为 1;负数截断为 0 |
position |
'right' |
'right' | 'left' |
重置为 'right' |
disabledDomains |
[] |
string[] | 非数组重置为 [] |
avoidExistingWidgets |
true |
boolean | — |
forceShow |
false |
boolean | — |
- Manifest 版本:3
- 扩展名称:Smart TOC & Scroll
- 版本号:2.13
- 所需权限:
activeTab、storage - 主机权限:
<all_urls>(所有 HTTP/HTTPS 页面) - Options 页面:
options.html - 图标规格:16px、32px、48px、128px(PNG 格式,路径
icons/icon{size}.png) - Content Scripts 注入:
catalog.js、theme.js(JS)+toc.css、themes.css(CSS)
注意:独立回顶按钮已并入
catalog.js主注入流程,manifest.json无需单独声明button.js。
本文档基于源码分析整理,已按 v2.13 当前行为更新,日期:2026-07-11。