diff --git a/plugins/articletoc.js b/plugins/articletoc.js index 67b9af0e..83ae1671 100644 --- a/plugins/articletoc.js +++ b/plugins/articletoc.js @@ -1,158 +1,188 @@ -function loadResource(type, attributes) { - if (type === 'style') { - const style = document.createElement('style'); - style.textContent = attributes.css; - document.head.appendChild(style); +(function () { + if (window.__GmeekTocReady) return; + window.__GmeekTocReady = true; + console.log("🍏 articletoc 插件已启用 https://code.buxiantang.top/"); + + const TOC_STYLE = ` + :root { + --toc-bg: rgba(255,255,255,0.8); + --toc-border: #e1e4e8; + --toc-text: #24292e; + --toc-hover: rgba(0,0,0,0.05); + --toc-icon-bg: rgba(255,255,255,0.8); + --toc-icon-color: #333; + --toc-icon-active-bg: #fff; + --toc-icon-active-color: #333; + } + @media (prefers-color-scheme: dark) { + :root { + --toc-bg: rgba(45, 51, 59, 0.8); + --toc-border: #444c56; + --toc-text: #adbac7; + --toc-hover: rgba(255, 255, 255, 0.05); + --toc-icon-bg: rgba(45, 51, 59, 0.8); + --toc-icon-color: #adbac7; + --toc-icon-active-bg: #2d333b; + --toc-icon-active-color: #adbac7; + } + } + .toc { + position: fixed; + bottom: 80px; + right: 20px; + width: 250px; + max-height: 70vh; + background-color: var(--toc-bg); + border: 1px solid var(--toc-border); + border-radius: 6px; + padding: 10px; + box-shadow: 0 2px 10px rgba(0,0,0,0.1); + overflow-y: auto; + z-index: 1000; + opacity: 0; + visibility: hidden; + transform: translateY(20px); + transition: opacity 0.3s ease, transform 0.3s ease, visibility 0.3s; + backdrop-filter: blur(5px); + } + .toc.show { + opacity: 1; + visibility: visible; + transform: translateY(0); + } + .toc a { + display: block; + color: var(--toc-text); + text-decoration: none; + padding: 5px 0; + font-size: 14px; + line-height: 1.5; + border-bottom: 1px solid var(--toc-border); + transition: background-color 0.2s ease, padding-left 0.2s ease; + } + .toc a:last-child { border-bottom: none; } + .toc a:hover { + background-color: var(--toc-hover); + padding-left: 5px; + } + .toc-icon { + position: fixed; + bottom: 20px; + right: 15px; + cursor: pointer; + background-color: var(--toc-icon-bg); + color: var(--toc-icon-color); + border-radius: 50%; + width: 50px; + height: 50px; + display: flex; + align-items: center; + justify-content: center; + box-shadow: 0 2px 10px rgba(0,0,0,0.1); + z-index: 1001; + transition: all 0.3s ease; + user-select: none; + -webkit-tap-highlight-color: transparent; + outline: none; + } + .toc-icon:hover { + transform: scale(1.1); + background-color: var(--toc-icon-active-bg); + } + .toc-icon:active { + transform: scale(0.9); + } + .toc-icon.active { + background-color: var(--toc-icon-active-bg); + color: var(--toc-icon-active-color); } -} + .toc-icon svg { + width: 24px; + height: 24px; + fill: none; + stroke: currentColor; + stroke-width: 2; + stroke-linecap: round; + stroke-linejoin: round; + } + @media (max-width: 768px) { + .toc { width: 200px; bottom: 70px; right: 10px; } + .toc-icon { width: 40px; height: 40px; bottom: 15px; right: 15px; } + .toc-icon svg { width: 20px; height: 20px; } + } + `; + + injectStyle(TOC_STYLE); + createTocIcon(); + observeMarkdown(); -function createTOC() { - const tocElement = document.createElement('div'); - tocElement.className = 'toc'; - const contentContainer = document.querySelector('.markdown-body'); - contentContainer.appendChild(tocElement); + function injectStyle(cssText) { + const style = document.createElement("style"); + style.textContent = cssText; + document.head.appendChild(style); + } - const headings = contentContainer.querySelectorAll('h1, h2, h3, h4, h5, h6'); - headings.forEach(heading => { - if (!heading.id) { - heading.id = heading.textContent.trim().replace(/\s+/g, '-').toLowerCase(); - } - const link = document.createElement('a'); - link.href = '#' + heading.id; - link.textContent = heading.textContent; - link.className = 'toc-link'; - link.style.paddingLeft = `${(parseInt(heading.tagName.charAt(1)) - 1) * 10}px`; - tocElement.appendChild(link); + function createTocIcon() { + const icon = document.createElement("div"); + icon.className = "toc-icon"; + icon.innerHTML = ''; + icon.onclick = (e) => { + e.stopPropagation(); + toggleTOC(); + }; + document.body.appendChild(icon); + document.addEventListener("click", (e) => { + const toc = document.querySelector(".toc"); + if (toc && toc.classList.contains("show") && !toc.contains(e.target) && !e.target.classList.contains("toc-icon")) { + toggleTOC(); + } }); -} + } -function toggleTOC() { - const tocElement = document.querySelector('.toc'); - const tocIcon = document.querySelector('.toc-icon'); - if (tocElement) { - tocElement.classList.toggle('show'); - tocIcon.classList.toggle('active'); - tocIcon.textContent = tocElement.classList.contains('show') ? '✖' : '☰'; - } -} + function observeMarkdown() { + const container = document.querySelector(".markdown-body"); + if (!container) return; -document.addEventListener("DOMContentLoaded", function() { - createTOC(); - const css = ` - :root { - --toc-bg: #fff; - --toc-border: #e1e4e8; - --toc-text: #24292e; - --toc-hover: #f6f8fa; - --toc-icon-bg: #fff; - --toc-icon-color: #ad6598; - --toc-icon-active-bg: #813c85; - --toc-icon-active-color: #fff; - } + const observer = new MutationObserver(() => { + const headings = container.querySelectorAll("h1,h2,h3,h4,h5,h6"); + if (headings.length > 0) { + renderTOC(container, headings); + observer.disconnect(); + } + }); - @media (prefers-color-scheme: dark) { - :root { - --toc-bg: #2d333b; - --toc-border: #444c56; - --toc-text: #adbac7; - --toc-hover: #373e47; - --toc-icon-bg: #22272e; - --toc-icon-color: #ad6598; - --toc-icon-active-bg: #813c85; - --toc-icon-active-color: #adbac7; - } - } + observer.observe(container, { childList: true, subtree: true }); + } - .toc { - position: fixed; - bottom: 60px; - right: 20px; - width: 250px; - max-height: 70vh; - background-color: var(--toc-bg); - border: 1px solid var(--toc-border); - border-radius: 6px; - padding: 10px; - box-shadow: 0 2px 10px rgba(0,0,0,0.1); - overflow-y: auto; - z-index: 1000; - opacity: 0; - visibility: hidden; - transform: translateY(20px) scale(0.9); - transition: opacity 0.3s ease, transform 0.3s ease, visibility 0.3s; - } - .toc.show { - opacity: 1; - visibility: visible; - transform: translateY(0) scale(1); - } - .toc a { - display: block; - color: var(--toc-text); - text-decoration: none; - padding: 5px 0; - font-size: 14px; - line-height: 1.5; - border-bottom: 1px solid var(--toc-border); - transition: background-color 0.2s ease, padding-left 0.2s ease; - } - .toc a:last-child { - border-bottom: none; - } - .toc a:hover { - background-color: var(--toc-hover); - padding-left: 5px; - } - .toc-icon { - position: fixed; - bottom: 20px; - right: 20px; - cursor: pointer; - font-size: 24px; - background-color: var(--toc-icon-bg); - color: var(--toc-icon-color); - border: 2px solid var(--toc-icon-color); - border-radius: 50%; - width: 40px; - height: 40px; - display: flex; - align-items: center; - justify-content: center; - box-shadow: 0 1px 3px rgba(0,0,0,0.12); - z-index: 1001; - transition: all 0.3s ease; - user-select: none; - -webkit-tap-highlight-color: transparent; - outline: none; - } - .toc-icon:hover { - transform: scale(1.1); - } - .toc-icon:active { - transform: scale(0.9); - } - .toc-icon.active { - background-color: var(--toc-icon-active-bg); - color: var(--toc-icon-active-color); - border-color: var(--toc-icon-active-bg); - transform: rotate(90deg); - } - `; - loadResource('style', {css: css}); + function renderTOC(container, headings) { + if (document.querySelector(".toc")) return; - const tocIcon = document.createElement('div'); - tocIcon.className = 'toc-icon'; - tocIcon.textContent = '☰'; - tocIcon.onclick = (e) => { - e.stopPropagation(); + const toc = document.createElement("div"); + toc.className = "toc"; + headings.forEach((h) => { + if (!h.id) h.id = h.textContent.trim().replace(/\s+/g, "-").toLowerCase(); + const link = document.createElement("a"); + link.href = "#" + h.id; + link.textContent = h.textContent; + link.style.paddingLeft = `${(parseInt(h.tagName[1]) - 1) * 10}px`; + link.onclick = (e) => { + e.preventDefault(); + document.getElementById(h.id)?.scrollIntoView({ behavior: "smooth" }); toggleTOC(); - }; - document.body.appendChild(tocIcon); - - document.addEventListener('click', (e) => { - const tocElement = document.querySelector('.toc'); - if (tocElement && tocElement.classList.contains('show') && !tocElement.contains(e.target) && !e.target.classList.contains('toc-icon')) { - toggleTOC(); - } + }; + toc.appendChild(link); }); -}); + container.appendChild(toc); + } + + function toggleTOC() { + const toc = document.querySelector(".toc"); + const icon = document.querySelector(".toc-icon"); + if (!toc || !icon) return; + toc.classList.toggle("show"); + icon.classList.toggle("active"); + icon.innerHTML = toc.classList.contains("show") + ? '' + : ''; + } +})();