2026-05-16 04:24:10 +08:00

365 lines
9.8 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!-- 文章导航按钮 -->
<mdui-button-icon id="toc-toggle">
<mdui-icon name="menu_book"></mdui-icon>
</mdui-button-icon>
<!-- 文章导航弹窗 -->
<mdui-dialog id="toc-dialog" close-on-esc close-on-overlay-click class="toc-dialog">
<div class="toc-header">
<mdui-typography variant="h6">文章导航</mdui-typography>
<mdui-button-icon class="close-btn" id="toc-close">
<mdui-icon name="close"></mdui-icon>
</mdui-button-icon>
</div>
<div class="toc-content">
<div id="toc-container" class="toc-container">
<!-- 目录内容将通过JavaScript动态加载 -->
<div class="toc-placeholder">
<mdui-icon name="menu_book"></mdui-icon>
<div>暂无文章导航</div>
</div>
</div>
</div>
<div class="toc-footer">
<mdui-button id="toc-close-btn">关闭</mdui-button>
</div>
</mdui-dialog>
<style>
.toc-dialog::part(overlay) {
backdrop-filter: blur(10px) saturate(180%);
-webkit-backdrop-filter: blur(10px) saturate(180%); /* Safari 支持 */
background: rgba(0, 0, 0, 0.4);
}
.toc-dialog {
--mdui-dialog-width: 500px;
--mdui-dialog-height: 70vh;
--mdui-dialog-max-height: 80vh;
}
.toc-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 20px 24px 0;
margin-bottom: 16px;
}
.toc-content {
padding: 0 24px;
flex: 1;
overflow: hidden;
}
.toc-container {
height: 100%;
overflow-y: auto;
padding: 8px 0;
}
.toc-container::-webkit-scrollbar {
width: 6px;
}
.toc-container::-webkit-scrollbar-track {
background: var(--mdui-color-surface-container);
}
.toc-container::-webkit-scrollbar-thumb {
background: var(--mdui-color-outline-variant);
border-radius: 3px;
}
.toc-placeholder {
text-align: center;
padding: 60px 20px;
color: var(--mdui-color-on-surface-variant);
}
.toc-placeholder mdui-icon {
font-size: 48px;
margin-bottom: 16px;
opacity: 0.5;
}
.toc-footer {
display: flex;
justify-content: flex-end;
padding: 16px 24px;
border-top: 1px solid var(--mdui-color-outline-variant);
}
.toc-list {
list-style: none;
padding-left: 0;
margin: 0;
}
.toc-list ul {
padding-left: 1.5rem;
margin: 0.5rem 0;
}
.toc-list li {
margin: 0.25rem 0;
}
.toc-link {
display: block;
padding: 0.5rem 0.75rem;
color: var(--mdui-color-on-surface);
text-decoration: none;
border-radius: 6px;
transition: all 0.2s ease;
}
.toc-link:hover {
background-color: var(--mdui-color-surface-container-hover);
color: var(--mdui-color-primary);
}
.toc-link.active {
background-color: var(--mdui-color-primary-container);
color: var(--mdui-color-on-primary-container);
font-weight: 500;
}
@media (max-width: 600px) {
.toc-dialog {
--mdui-dialog-width: 95vw;
--mdui-dialog-height: 80vh;
}
.toc-header {
padding: 16px 16px 0;
}
.toc-content {
padding: 0 16px;
}
.toc-footer {
padding: 12px 16px;
}
}
</style>
<script>
class HexoTOC {
constructor() {
this.dialog = document.getElementById('toc-dialog');
this.tocContainer = document.getElementById('toc-container');
this.init();
}
init() {
this.bindEvents();
}
bindEvents() {
const tocToggle = document.getElementById('toc-toggle');
const tocClose = document.getElementById('toc-close');
const tocCloseBtn = document.getElementById('toc-close-btn');
if (tocToggle) {
tocToggle.addEventListener('click', () => {
this.open();
});
}
if (tocClose) {
tocClose.addEventListener('click', () => {
this.close();
});
}
if (tocCloseBtn) {
tocCloseBtn.addEventListener('click', () => {
this.close();
});
}
// 监听ESC键关闭
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape' && this.isOpen()) {
this.close();
}
});
}
open() {
if (this.dialog) {
this.dialog.open = true;
// 加载当前页面的目录
this.loadCurrentPageTOC();
}
}
close() {
if (this.dialog) {
this.dialog.open = false;
}
}
isOpen() {
return this.dialog && this.dialog.open;
}
loadCurrentPageTOC() {
// 检查是否在文章页面
if (typeof window.page !== 'undefined' && window.page.title) {
// 从页面内容中提取标题
this.extractTOCFromContent();
} else {
// 显示占位符
this.tocContainer.innerHTML = `
<div class="toc-placeholder">
<mdui-icon name="menu_book"></mdui-icon>
<div>暂无文章导航</div>
</div>
`;
}
}
extractTOCFromContent() {
// 获取文章内容
const content = document.querySelector('.article-content');
if (!content) {
this.tocContainer.innerHTML = `
<div class="toc-placeholder">
<mdui-icon name="menu_book"></mdui-icon>
<div>暂无文章导航</div>
</div>
`;
return;
}
// 提取标题元素
const headers = content.querySelectorAll('h1, h2, h3, h4, h5, h6');
if (headers.length === 0) {
this.tocContainer.innerHTML = `
<div class="toc-placeholder">
<mdui-icon name="menu_book"></mdui-icon>
<div>暂无文章导航</div>
</div>
`;
return;
}
// 构建目录HTML
let tocHTML = '<ul class="toc-list">';
headers.forEach((header, index) => {
// 为标题添加ID如果没有
if (!header.id) {
header.id = 'toc-header-' + index;
}
const level = parseInt(header.tagName.charAt(1));
const indent = (level - 1) * 20; // 根据标题级别设置缩进
tocHTML += `
<li style="margin-left: ${indent}px;">
<a href="#${header.id}" class="toc-link" data-target="${header.id}">
${header.textContent}
</a>
</li>
`;
});
tocHTML += '</ul>';
this.tocContainer.innerHTML = tocHTML;
// 绑定导航链接事件
this.bindTOCEvents();
}
bindTOCEvents() {
const links = this.tocContainer.querySelectorAll('.toc-link');
links.forEach(link => {
link.addEventListener('click', (e) => {
e.preventDefault();
const targetId = link.getAttribute('data-target');
if (targetId) {
const target = document.getElementById(targetId);
if (target) {
// 平滑滚动到目标元素
target.scrollIntoView({
behavior: 'smooth',
block: 'center'
});
// 关闭对话框
this.close();
}
}
});
});
// 高亮当前正在阅读的标题
this.highlightCurrentHeader();
window.addEventListener('scroll', this.throttle(() => {
this.highlightCurrentHeader();
}, 100));
}
// 节流函数,防止滚动事件过于频繁
throttle(fn, wait) {
let last = 0;
return function(...args) {
const now = Date.now();
if (now - last > wait) {
last = now;
fn.apply(this, args);
}
};
}
// 高亮当前正在阅读的标题
highlightCurrentHeader() {
const headers = Array.from(document.querySelectorAll('.article-content h1, .article-content h2, .article-content h3, .article-content h4, .article-content h5, .article-content h6'));
if (!headers.length) return;
const scrollY = window.scrollY || window.pageYOffset;
const offset = 80; // 可根据实际导航栏高度调整
let currentId = headers[0].id;
for (let i = 0; i < headers.length; i++) {
const rect = headers[i].getBoundingClientRect();
const top = rect.top + scrollY - offset;
if (scrollY >= top) {
currentId = headers[i].id;
} else {
break;
}
}
// 移除所有高亮
this.tocContainer.querySelectorAll('.toc-link').forEach(link => {
link.classList.remove('active');
});
// 高亮当前
const activeLink = this.tocContainer.querySelector('.toc-link[data-target="' + currentId + '"]');
if (activeLink) {
activeLink.classList.add('active');
// 滚动目录到可见区域
const tocScroll = this.tocContainer;
const linkRect = activeLink.getBoundingClientRect();
const tocRect = tocScroll.getBoundingClientRect();
if (linkRect.top < tocRect.top || linkRect.bottom > tocRect.bottom) {
tocScroll.scrollTop = activeLink.offsetTop - tocScroll.clientHeight / 2 + activeLink.clientHeight / 2;
}
}
}
}
// 初始化
document.addEventListener('DOMContentLoaded', () => {
window.hexoTOC = new HexoTOC();
});
// 页面加载完成后再次初始化,确保能获取到文章内容
window.addEventListener('load', () => {
if (!window.hexoTOC) {
window.hexoTOC = new HexoTOC();
}
});
</script>