diff --git a/assets/css/_core/_media.scss b/assets/css/_core/_media.scss index bf55c170..5c29f081 100644 --- a/assets/css/_core/_media.scss +++ b/assets/css/_core/_media.scss @@ -40,12 +40,6 @@ } } -@media only screen and (min-width: 961px) { - #toc-drawer-button { - display: none; - } -} - @media only screen and (max-width: 960px) { %page-style { width: ROUND(80%, 2px) !important; diff --git a/assets/css/_partials/_single/_toc.scss b/assets/css/_partials/_single/_toc.scss index c0f39d48..55be0547 100644 --- a/assets/css/_partials/_single/_toc.scss +++ b/assets/css/_partials/_single/_toc.scss @@ -1,3 +1,4 @@ +// Common styles for all TOC types .toc { .toc-title { font-size: $toc-title-font-size; @@ -70,6 +71,48 @@ } } +// Common active state styles for auto and dialog TOC +#toc-auto, +#toc-dialog { + .toc-content { + li.has-active { + &::marker { + color: $single-link-color; + + [data-theme='dark'] & { + color: $single-link-color-dark; + } + } + + &:has(> .active)::marker { + font-weight: bold; + } + + > a { + color: $single-link-color; + + [data-theme='dark'] & { + color: $single-link-color-dark; + } + } + } + + ul { + a.active::before { + color: $single-link-hover-color; + + [data-theme='dark'] & { + color: $single-link-hover-color-dark; + } + } + } + + a.active { + font-weight: bold; + } + } +} + #toc-auto { display: block; position: sticky; @@ -127,28 +170,6 @@ margin: 0; } - li.has-active { - &::marker { - color: $single-link-color; - - [data-theme='dark'] & { - color: $single-link-color-dark; - } - } - - &:has(> .active)::marker { - font-weight: bold; - } - - > a { - color: $single-link-color; - - [data-theme='dark'] & { - color: $single-link-color-dark; - } - } - } - // for unordered list automatically collapsed ul { ul { @@ -158,14 +179,6 @@ .has-active > ul { display: block; } - - a.active::before { - color: $single-link-hover-color; - - [data-theme='dark'] & { - color: $single-link-hover-color-dark; - } - } } // for ordered list automatically collapsed @@ -178,10 +191,6 @@ display: block; } } - - a.active { - font-weight: bold; - } } } @@ -261,11 +270,11 @@ opacity 0.2s 0.4s; opacity: 0; translate: 100vw 0; - width: 100%; + width: MIN(calc(100% - 4rem), 500px); height: 100%; max-width: 100%; max-height: 100%; - margin-left: 4rem; + margin-right: 0; border: none; .toc { diff --git a/assets/js/theme.js b/assets/js/theme.js index 0ecedb4a..14a3985c 100644 --- a/assets/js/theme.js +++ b/assets/js/theme.js @@ -171,7 +171,7 @@ class FixIt { window.postChatUser.setSearchInput(''); }, false); } - return; + return; } if (isMobile) { @@ -209,9 +209,9 @@ class FixIt { $searchClear.style.display = 'none'; this._searchDesktop && this._searchDesktop.autocomplete.setVal(''); }, false); - this._searchDesktopOnClickMask = this._searchDesktopOnClickMask ||(() => { - this._resetSearchUI($header, $searchLoading, $searchClear, this._searchDesktop); - }); + this._searchDesktopOnClickMask = this._searchDesktopOnClickMask || (() => { + this._resetSearchUI($header, $searchLoading, $searchClear, this._searchDesktop); + }); this.clickMaskEventSet.add(this._searchDesktopOnClickMask); } $searchInput.addEventListener('input', () => { @@ -342,7 +342,7 @@ class FixIt { suggestion: ({ title, uri, date, context }) => `
${title}${date}
${context}
`, empty: ({ query }) => `
${searchConfig.noResultsFound}: "${query}"
`, - footer: ({}) => { + footer: ({ }) => { let searchType, icon, href; switch (searchConfig.type) { case 'algolia': @@ -547,7 +547,7 @@ class FixIt { let code = stagingDOM.contentAsText(); try { code = JSON.stringify(JSON.parse(code), null, 2); - } catch {} + } catch { } this.util.copyText(code).then(() => { $btn.toggleAttribute('data-copied', true); setTimeout(() => { @@ -579,6 +579,46 @@ class FixIt { // TODO } + /** + * Helper method to update TOC active state based on scroll position + * @param {HTMLElement} $tocContainer - TOC container element for parent traversal + * @param {HTMLCollection} $headingElements - Heading elements to track + * @param {number} indexOffset - Offset for active state calculation + */ + _updateTocActiveState($tocContainer, $headingElements, indexOffset) { + const $tocLinkElements = $tocContainer.querySelectorAll('a:first-child'); + const $tocLiElements = $tocContainer.getElementsByTagName('li'); + + // Remove all active classes + this.util.forEach($tocLinkElements, ($tocLink) => { + $tocLink.classList.remove('active'); + }); + this.util.forEach($tocLiElements, ($tocLi) => { + $tocLi.classList.remove('has-active'); + }); + + // Calculate active TOC index + let activeTocIndex = $headingElements.length - 1; + for (let i = 0; i < $headingElements.length - 1; i++) { + const thisTop = $headingElements[i].getBoundingClientRect().top; + const nextTop = $headingElements[i + 1].getBoundingClientRect().top; + if ((i == 0 && thisTop > indexOffset) || (thisTop <= indexOffset && nextTop > indexOffset)) { + activeTocIndex = i; + break; + } + } + + // Add active classes + if (activeTocIndex !== -1 && $tocLinkElements[activeTocIndex]) { + $tocLinkElements[activeTocIndex].classList.add('active'); + let $parent = $tocLinkElements[activeTocIndex].parentElement; + while ($parent !== $tocContainer) { + $parent.classList.add('has-active'); + $parent = $parent.parentElement.parentElement; + } + } + } + /** * init table of contents */ @@ -587,61 +627,53 @@ class FixIt { if ($tocCore === null) { return; } - if (document.getElementById('toc-static').dataset.kept === 'true' || this.util.isTocStatic()) { + const $headingElements = document.getElementsByClassName('heading-element'); + const INDEX_OFFSET = 20 + this.breadcrumbHeight + ( + document.body.dataset.headerDesktop !== 'normal' ? document.getElementById('header-desktop').offsetHeight : 0 + ); + + // TOC Static and TOC Dialog + if (this.util.isTocStatic()) { const $tocContentStatic = document.getElementById('toc-content-static'); if ($tocCore.parentElement !== $tocContentStatic) { $tocCore.parentElement.removeChild($tocCore); $tocContentStatic.appendChild($tocCore); } + this._tocDialogOnScroll = this._tocDialogOnScroll || (() => { + this._updateTocActiveState( + document.querySelector('#toc-content-drawer>nav'), + $headingElements, + INDEX_OFFSET + ); + }); + this._tocDialogOnScroll(); + this.scrollEventSet.add(this._tocDialogOnScroll); this._tocOnScroll && this.scrollEventSet.delete(this._tocOnScroll); - } else { - const $tocContentAuto = document.getElementById('toc-content-auto'); - if ($tocCore.parentElement !== $tocContentAuto) { - $tocCore.parentElement.removeChild($tocCore); - $tocContentAuto.appendChild($tocCore); - } - const $toc = document.getElementById('toc-auto'); - $toc.style.visibility = 'visible'; - this.util.animateCSS($toc, ['animate__fadeIn', 'animate__faster'], true); - const $postMeta = document.querySelector('.post-meta'); - $toc.style.marginTop = `${$postMeta.offsetTop + $postMeta.clientHeight}px`; - const $tocLinkElements = $tocCore.querySelectorAll('a:first-child'); - const $tocLiElements = $tocCore.getElementsByTagName('li'); - const $headingElements = document.getElementsByClassName('heading-element'); - const headerHeight = document.getElementById('header-desktop').offsetHeight; - document.querySelector('.fi-container').addEventListener('resize', () => { - $toc.style.marginBottom = `${document.querySelector('.fi-container').clientHeight - document.querySelector('.post-footer').offsetTop}px`; - }); - this._tocOnScroll = this._tocOnScroll || (() => { - $toc.style.marginBottom = `${document.querySelector('.fi-container').clientHeight - document.querySelector('.post-footer').offsetTop}px`; - this.util.forEach($tocLinkElements, ($tocLink) => { - $tocLink.classList.remove('active'); - }); - this.util.forEach($tocLiElements, ($tocLi) => { - $tocLi.classList.remove('has-active'); - }); - const INDEX_SPACING = 20 + (document.body.dataset.headerDesktop !== 'normal' ? headerHeight : 0) + this.breadcrumbHeight; - let activeTocIndex = $headingElements.length - 1; - for (let i = 0; i < $headingElements.length - 1; i++) { - const thisTop = $headingElements[i].getBoundingClientRect().top; - const nextTop = $headingElements[i + 1].getBoundingClientRect().top; - if ((i == 0 && thisTop > INDEX_SPACING) || (thisTop <= INDEX_SPACING && nextTop > INDEX_SPACING)) { - activeTocIndex = i; - break; - } - } - if (activeTocIndex !== -1 && $tocLinkElements[activeTocIndex]) { - $tocLinkElements[activeTocIndex].classList.add('active'); - let $parent = $tocLinkElements[activeTocIndex].parentElement; - while ($parent !== $tocCore) { - $parent.classList.add('has-active'); - $parent = $parent.parentElement.parentElement; - } - } - }); - this._tocOnScroll(); - this.scrollEventSet.add(this._tocOnScroll); + return; } + + // TOC Auto + const $tocContentAuto = document.getElementById('toc-content-auto'); + if ($tocCore.parentElement !== $tocContentAuto) { + $tocCore.parentElement.removeChild($tocCore); + $tocContentAuto.appendChild($tocCore); + } + const $toc = document.getElementById('toc-auto'); + $toc.style.visibility = 'visible'; + this.util.animateCSS($toc, ['animate__fadeIn', 'animate__faster'], true); + const $postMeta = document.querySelector('.post-meta'); + $toc.style.marginTop = `${$postMeta.offsetTop + $postMeta.clientHeight}px`; + + document.querySelector('.fi-container').addEventListener('resize', () => { + $toc.style.marginBottom = `${document.querySelector('.fi-container').clientHeight - document.querySelector('.post-footer').offsetTop}px`; + }); + this._tocOnScroll = this._tocOnScroll || (() => { + $toc.style.marginBottom = `${document.querySelector('.fi-container').clientHeight - document.querySelector('.post-footer').offsetTop}px`; + this._updateTocActiveState($tocCore, $headingElements, INDEX_OFFSET); + }); + this._tocOnScroll(); + this.scrollEventSet.add(this._tocOnScroll); + this._tocDialogOnScroll && this.scrollEventSet.delete(this._tocDialogOnScroll); } initTocListener() { @@ -670,6 +702,7 @@ class FixIt { if (!dialog || !openButton) { return; } + openButton.classList.toggle('d-none', !this.util.isTocStatic()); openButton.addEventListener("click", () => { dialog.showModal(); document.activeElement?.blur(); @@ -798,7 +831,7 @@ class FixIt { if (Array.isArray(markerArray) && markerArray.length > 0) { markerArray.forEach(marker => { const { lng: markerLng, lat: markerLat, description } = marker; - const popup = new mapboxgl.Popup({ offset: 25 }).setText(description); + const popup = new mapboxgl.Popup({ offset: 25 }).setText(description); new mapboxgl.Marker() .setLngLat([markerLng, markerLat]) .setPopup(popup) @@ -1023,7 +1056,7 @@ class FixIt { if (this.config.comment.giscus) { const giscusConfig = this.config.comment.giscus; this._giscusOnSwitchTheme = this._giscusOnSwitchTheme || (() => { - const message = { setConfig: { theme: this.isDark ? giscusConfig.darkTheme : giscusConfig.lightTheme }}; + const message = { setConfig: { theme: this.isDark ? giscusConfig.darkTheme : giscusConfig.lightTheme } }; document.querySelector('.giscus-frame')?.contentWindow.postMessage({ giscus: message }, giscusConfig.origin); }); this.switchThemeEventSet.add(this._giscusOnSwitchTheme); @@ -1031,7 +1064,7 @@ class FixIt { this._messageListener = (event) => { if (event.origin !== giscusConfig.origin) return; const $script = document.querySelector('#giscus>script'); - if ($script){ + if ($script) { $script.parentElement.removeChild($script); } this._giscusOnSwitchTheme() @@ -1089,8 +1122,8 @@ class FixIt { navigator.serviceWorker .ready .then(function (registration) { - // console.log('Service Worker Ready'); - }); + // console.log('Service Worker Ready'); + }); } } @@ -1206,7 +1239,7 @@ class FixIt { const scrollTop = Number(window.sessionStorage?.getItem(`fixit-bookmark/#${location.pathname}`)); // If the page opens with a specific hash, just jump out if (scrollTop && location.hash === '') { - window.scrollTo({ + window.scrollTo({ top: scrollTop, behavior: 'smooth' }); @@ -1253,7 +1286,7 @@ class FixIt { if (targetFrame) { window.postChatUser.setPostChatTheme(isDark ? 'dark' : 'light'); } else { - postChat_theme = isDark ? 'dark' : 'light'; + postChat_theme = isDark ? 'dark' : 'light'; } }); } diff --git a/assets/js/util.js b/assets/js/util.js index f6586cb9..57c9344e 100644 --- a/assets/js/util.js +++ b/assets/js/util.js @@ -20,7 +20,7 @@ export default class Util { } isTocStatic() { - return window.matchMedia('only screen and (max-width: 960px)').matches; + return document.getElementById('toc-static').dataset.kept === 'true' || window.matchMedia('only screen and (max-width: 960px)').matches; } /** diff --git a/layouts/_partials/init/index.html b/layouts/_partials/init/index.html index 2a62b4fc..f1983b4d 100644 --- a/layouts/_partials/init/index.html +++ b/layouts/_partials/init/index.html @@ -1,4 +1,4 @@ -{{- hugo.Store.Set "version" "v0.4.0-alpha.3-20251119064345-c8223a75" -}} +{{- hugo.Store.Set "version" "v0.4.0-alpha.3-20251119082959-b78f22b6" -}} {{- .Store.Set "this" dict -}} {{- partial "init/detection-env.html" . -}} diff --git a/layouts/_partials/widgets.html b/layouts/_partials/widgets.html index 9c303850..81786064 100644 --- a/layouts/_partials/widgets.html +++ b/layouts/_partials/widgets.html @@ -18,7 +18,7 @@ {{- end -}} {{- /* toc dialog button */ -}} {{- if .Page.Store.Get "enableTocDialog" -}} -
+
{{- dict "Class" "fa-solid fa-bars fa-fw" | partial "plugin/icon.html" -}}
{{- end -}}