feat: update TOC styles and improve active state handling in dialog

This commit is contained in:
Cell
2025-11-19 16:29:58 +08:00
parent b78f22b698
commit 237c2f3d13
6 changed files with 142 additions and 106 deletions
-6
View File
@@ -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;
+45 -36
View File
@@ -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 {
+94 -61
View File
@@ -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 }) =>
`<div><a href="${uri}"><span class="suggestion-title">${title}</span></a><span class="suggestion-date">${date}</span></div><div class="suggestion-context">${context}</div>`,
empty: ({ query }) => `<div class="search-empty">${searchConfig.noResultsFound}: <span class="search-query">"${query}"</span></div>`,
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';
}
});
}
+1 -1
View File
@@ -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;
}
/**
+1 -1
View File
@@ -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" . -}}
+1 -1
View File
@@ -18,7 +18,7 @@
{{- end -}}
{{- /* toc dialog button */ -}}
{{- if .Page.Store.Get "enableTocDialog" -}}
<div id="toc-drawer-button" class="fixed-button toc-drawer-button{{ with $params.password }} encrypted-hidden{{ end }}" role="button" aria-label="{{ T `single.contents` }}">
<div id="toc-drawer-button" class="fixed-button toc-drawer-button d-none{{ with $params.password }} encrypted-hidden{{ end }}" role="button" aria-label="{{ T `single.contents` }}">
{{- dict "Class" "fa-solid fa-bars fa-fw" | partial "plugin/icon.html" -}}
</div>
{{- end -}}