diff --git a/generator/_assets/js/custom.js b/generator/_assets/js/custom.js index 40de9c667..80138dad6 100755 --- a/generator/_assets/js/custom.js +++ b/generator/_assets/js/custom.js @@ -309,11 +309,16 @@ function selectVersion(value) { } }; + document.addEventListener("DOMContentLoaded", function () { fillVersionWrapperSelect('/docs/branches.json') - document.querySelectorAll(".article h1, .article h2, .article h3, .article h4, .article h5, .article h6").forEach(function(el){ - var url = new URL(window.location.href); + const anchors = document.querySelectorAll( + ".article h1[id], .article h2[id], .article h3[id], .article h4[id], .article h5[id], .article h6[id]" + ); + + anchors.forEach(function(el){ + const url = new URL(window.location.href); el.insertAdjacentHTML('beforeend', ''); }); @@ -324,5 +329,78 @@ document.addEventListener("DOMContentLoaded", function () { a.classList.add('url-copied'); setTimeout(function () { a.classList.remove('url-copied') }, 2000); } - }) + }); + + /** + * Highlight the current TOC item when a user scrolls to the corresponding page section. + */ + (() => { + const tocLinks = document.querySelectorAll('#TOCbox_list li a'); + + if (!tocLinks || !anchors) { + return; + } + + // offsetTop returns offset to the offsetParent, which is main wrapper, we need to add 130px to get actual offset + const fetchOffsets = anchors => [...anchors].map(a => a.offsetTop + 130); + let anchorsOffsets = fetchOffsets(anchors); + + let timeout = undefined; + const updateActiveTocItem = () => { + if (timeout) { + clearTimeout(timeout) + } + + // The current TOC menu item will be calculated in 100 ms after the user stops scrolling. + // Otherwise, there might be redundant calculations. + timeout = setTimeout( () => { + let scrollTop = window.scrollY; + tocLinks.forEach(link => link.classList.remove('current')); + + for (let i = anchorsOffsets.length - 1; i >= 0; i--) { + if (scrollTop > anchorsOffsets[i]) { + setActiveLink(anchors[i].id); + break; + } + } + }, 50); // 0.05s threshold + + } + + const setActiveLink = (id) => { + const activeLink = document.querySelector(`#TOCbox_list li a[href$="#${id}"]`); + if (activeLink) { + activeLink.classList.add('current'); + } + } + + window.addEventListener('scroll', updateActiveTocItem); + window.addEventListener("resize", () => { + // anchors position change when the window is resized + anchorsOffsets = fetchOffsets(anchors); + }); + })(); + + /** + * Display scroll to top button when the scroll reaches 350px + * from the top and window width less than 1280px + */ + (() => { + const scrollToTopBtn = document.getElementById('scrollToTopBtn'); + const showClass = 'show'; + + if (!scrollToTopBtn) { + return; + } + + const handleScrollToTopVisibility = () => { + if (window.scrollY > 350 && window.innerWidth <= 1280) { + scrollToTopBtn.classList.add(showClass); + } else { + scrollToTopBtn.classList.remove(showClass); + } + } + + window.addEventListener('scroll', handleScrollToTopVisibility); + })(); }); diff --git a/generator/_assets/styles/less/base.less b/generator/_assets/styles/less/base.less index 9d23a898d..af0cef9db 100755 --- a/generator/_assets/styles/less/base.less +++ b/generator/_assets/styles/less/base.less @@ -452,3 +452,25 @@ article { margin-right: 0 !important; } } + +#scrollToTopBtn { + position: fixed; + bottom: 4rem; + right: 3.2rem; + height: 4.4rem; + width: 4.4rem; + background-color: #aaa; + color: #fff; + border: none; + border-radius: 50%; + cursor: pointer; + transform: scale(0); + transition: all .6s ease-out 0s; + i { + font-size: 1.6rem; + } + &.show { + transform: scale(1); + transition: all .6s ease-in 0s; + } +} \ No newline at end of file diff --git a/generator/_assets/styles/less/pages.less b/generator/_assets/styles/less/pages.less index ddd5c7fb7..ba4381070 100755 --- a/generator/_assets/styles/less/pages.less +++ b/generator/_assets/styles/less/pages.less @@ -77,15 +77,14 @@ } &-title { - font-weight: 400; - font-size: 16px; - line-height: 24px; - border-bottom: 1px solid #E2E2E2; + font-weight: 500; + font-size: 14px; + line-height: 22px; margin-bottom: .6rem; + display: inline-block; @media @desktop-down { font-weight: 500; line-height: 4.5rem; - background: #F8F8FA; padding-left: .8rem; position: relative; cursor: pointer; @@ -93,7 +92,7 @@ content: "\F286"; font-family: bootstrap-icons; position: absolute; - right: 2rem; + right: -2.8rem; top: 50%; transform: translateY(-50%); } @@ -117,56 +116,57 @@ } #TOCbox_list { - background: #F7F7F7; border-radius: 2px; - color: @blue-700; + color: @gray-600; font-weight: 500; font-size: 14px; line-height: 22px; - padding: .8rem 1.2rem; - max-height: 420px; + padding: .8rem 0; overflow: auto; ul { list-style: none; + border-left: 1px solid #E1E1E1; } li { margin-bottom: 8px; + padding-left: 2.4rem; + &:has(.current) { + border-left: 3px solid @blue-700; + transition: border 0.1s linear; + } } a { - color: @blue-700; - font-weight: 500; + color: @gray-600; + font-weight: 400; font-size: 14px; line-height: 22px; text-decoration: none; + display: inline-block; &:hover { - border-bottom: 1px solid @blue-700; + text-decoration: underline; + border: none; + } + + &.current { + color: @blue-700; + font-weight: 700; + margin-left: -.3rem } } } #TOCbox_list .link_h3, #TOCbox_list_mobile .sidr-class-link_h3 { - margin-left: 1rem; - } - - #TOCbox_list .link_h3:before, - #TOCbox_list_mobile .sidr-class-link_h3:before, - #TOCbox_list .link_h4:before, - #TOCbox_list_mobile .sidr-class-link_h4:before { - font-size: 11px; - margin: 0 6px 0 0; - font-family: bootstrap-icons; - content: "\F280"; - color: #000000; + padding-left: 4rem; } #TOCbox_list .link_h4, #TOCbox_list_mobile .sidr-class-link_h4 { - margin-left: 2rem; + padding-left: 5.6rem; } } }