File: /home/elrashedytravel/public_html/wp-content/themes/custom-functions-1767952375/js/toc.js
// Table of Contents Generator
class TableOfContents {
constructor() {
this.tocContainer = document.querySelector('#table-of-contents.toc-container');
this.tocList = document.getElementById('toc-list');
this.entryContent = document.querySelector('.entry-content');
this.tocToggle = document.querySelector('.toc-toggle');
this.tocToggleIcon = document.querySelector('.toc-toggle-icon');
this.headings = [];
this.isCollapsed = false;
this.init();
}
init() {
if (!this.entryContent || !this.tocContainer) return;
this.generateTOC();
this.bindEvents();
this.setupScrollSpy();
// 如果沒有標題,隱藏整個TOC側邊欄
if (this.headings.length === 0) {
const tocSidebar = document.querySelector('.toc-sidebar');
if (tocSidebar) {
tocSidebar.style.display = 'none';
}
}
}
generateTOC() {
// 查找所有標題
this.headings = Array.from(this.entryContent.querySelectorAll('h1, h2, h3, h4, h5, h6'));
if (this.headings.length === 0) return;
// 為每個標題添加ID
this.headings.forEach((heading, index) => {
if (!heading.id) {
heading.id = `heading-${index}`;
}
});
// 生成TOC HTML
const tocHTML = this.buildTOCHTML();
this.tocList.innerHTML = tocHTML;
}
buildTOCHTML() {
if (this.headings.length === 0) return '';
// 構建層級結構
const tocStructure = this.buildTOCStructure();
return this.renderTOCStructure(tocStructure);
}
buildTOCStructure() {
const structure = [];
const stack = [{ level: 0, children: structure }];
this.headings.forEach((heading, index) => {
const level = parseInt(heading.tagName.charAt(1));
const text = heading.textContent.trim();
const id = heading.id;
const item = {
level,
text,
id,
children: []
};
// 找到正確的父級
while (stack.length > 1 && stack[stack.length - 1].level >= level) {
stack.pop();
}
// 添加到當前層級
stack[stack.length - 1].children.push(item);
stack.push(item);
});
return structure;
}
renderTOCStructure(items) {
if (!items || items.length === 0) return '';
let html = '<ol class="toc-list">';
items.forEach(item => {
html += `
<li class="toc-item toc-level-${item.level}">
<a href="#${item.id}" class="toc-link" data-heading-id="${item.id}">
<span class="toc-number"></span>
<span class="toc-text">${item.text}</span>
</a>
`;
// 遞歸渲染子項目
if (item.children && item.children.length > 0) {
html += this.renderTOCStructure(item.children);
}
html += '</li>';
});
html += '</ol>';
return html;
}
bindEvents() {
// TOC折疊切換
if (this.tocToggle) {
this.tocToggle.addEventListener('click', () => {
this.toggleTOC();
});
}
// 點擊TOC鏈接平滑滾動
this.tocList.addEventListener('click', (e) => {
if (e.target.closest('.toc-link')) {
e.preventDefault();
const link = e.target.closest('.toc-link');
const targetId = link.getAttribute('data-heading-id');
this.scrollToHeading(targetId);
}
});
}
toggleTOC() {
this.isCollapsed = !this.isCollapsed;
this.tocContainer.classList.toggle('toc-collapsed', this.isCollapsed);
if (this.tocToggleIcon) {
this.tocToggleIcon.textContent = this.isCollapsed ? '+' : '−';
}
}
scrollToHeading(headingId) {
const target = document.getElementById(headingId);
if (target) {
const offset = 80; // 頁面頂部偏移量
const targetPosition = target.getBoundingClientRect().top + window.pageYOffset - offset;
window.scrollTo({
top: targetPosition,
behavior: 'smooth'
});
}
}
setupScrollSpy() {
// 創建Intersection Observer來監控標題
const observerOptions = {
rootMargin: '-80px 0px -60% 0px',
threshold: 0
};
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
const id = entry.target.id;
const tocLink = this.tocList.querySelector(`[data-heading-id="${id}"]`);
if (tocLink) {
if (entry.isIntersecting) {
// 移除所有active類
this.tocList.querySelectorAll('.toc-link').forEach(link => {
link.classList.remove('active');
});
// 添加當前active類
tocLink.classList.add('active');
// 滾動TOC到可見區域
this.scrollTOCIntoView(tocLink);
}
}
});
}, observerOptions);
// 觀察所有標題
this.headings.forEach(heading => {
observer.observe(heading);
});
}
scrollTOCIntoView(tocLink) {
const tocNav = this.tocContainer.querySelector('.toc-nav');
if (!tocNav || !tocLink) return;
const tocNavRect = tocNav.getBoundingClientRect();
const linkRect = tocLink.getBoundingClientRect();
// 檢查鏈接是否在可見區域外
if (linkRect.top < tocNavRect.top || linkRect.bottom > tocNavRect.bottom) {
// 計算需要滾動的距離,讓active項目在TOC中央
const scrollTop = tocNav.scrollTop + linkRect.top - tocNavRect.top - (tocNavRect.height / 2) + (linkRect.height / 2);
tocNav.scrollTo({
top: scrollTop,
behavior: 'smooth'
});
}
}
}
// 當DOM加載完成後初始化TOC
document.addEventListener('DOMContentLoaded', () => {
// 檢查是否在單篇文章或頁面
if (document.body.classList.contains('single') ||
document.body.classList.contains('single-post') ||
document.body.classList.contains('page')) {
new TableOfContents();
}
});