diff --git a/assets/css/compiled/main.css b/assets/css/compiled/main.css index ee130305..fe2cea7a 100644 --- a/assets/css/compiled/main.css +++ b/assets/css/compiled/main.css @@ -860,9 +860,6 @@ video { .hx-justify-center { justify-content: center; } -.hx-justify-between { - justify-content: space-between; -} .hx-justify-items-start { justify-items: start; } @@ -1028,10 +1025,6 @@ video { --tw-bg-opacity: 1; background-color: rgb(255 247 237 / var(--tw-bg-opacity)); } -.hx-bg-primary-100 { - --tw-bg-opacity: 1; - background-color: hsl(var(--primary-hue) var(--primary-saturation) calc(var(--primary-lightness) + calc(calc(100% - var(--primary-lightness)) / 50) * 44) / var(--tw-bg-opacity)); -} .hx-bg-primary-400 { --tw-bg-opacity: 1; background-color: hsl(var(--primary-hue) var(--primary-saturation) calc(var(--primary-lightness) + calc(calc(100% - var(--primary-lightness)) / 50) * 16) / var(--tw-bg-opacity)); @@ -1311,10 +1304,6 @@ video { --tw-text-opacity: 1; color: rgb(154 52 18 / var(--tw-text-opacity)); } -.hx-text-primary-800 { - --tw-text-opacity: 1; - color: hsl(var(--primary-hue) var(--primary-saturation) calc(calc(var(--primary-lightness) / 50) * 32) / var(--tw-text-opacity)); -} .hx-text-red-900 { --tw-text-opacity: 1; color: rgb(127 29 29 / var(--tw-text-opacity)); @@ -2483,7 +2472,7 @@ article details > summary::before { color: hsl(var(--primary-hue) var(--primary-saturation) calc(calc(var(--primary-lightness) / 50) * 45) / var(--tw-text-opacity)); } @media (max-width: 767px) { - .sidebar-container { + .hextra-sidebar-container { position: fixed; top: 0px; bottom: 0px; @@ -2494,28 +2483,152 @@ article details > summary::before { background-color: rgb(255 255 255 / var(--tw-bg-opacity)); padding-top: calc(var(--navbar-height)); } - .sidebar-container:is(html[class~="dark"] *) { + .hextra-sidebar-container:is(html[class~="dark"] *) { --tw-bg-opacity: 1; background-color: rgb(17 17 17 / var(--tw-bg-opacity)); } - .sidebar-container { + .hextra-sidebar-container { transition: transform 0.8s cubic-bezier(0.52, 0.16, 0.04, 1); will-change: transform, opacity; contain: layout style; backface-visibility: hidden; } } -.sidebar-container li > div { +.hextra-sidebar-container li > div { height: 0px; } -.sidebar-container li.open > div { +.hextra-sidebar-container li.open > div { height: auto; padding-top: 0.25rem; } -.sidebar-container li.open > a > span > svg > path { +.hextra-sidebar-container li.open > a > span > svg > path { --tw-rotate: 90deg; transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); } +.hextra-sidebar-container .hextra-sidebar-item-list { + position: relative; + display: flex; + flex-direction: column; + gap: 0.25rem; +} +.hextra-sidebar-container .hextra-sidebar-item-list::before { + position: absolute; + top: 0.25rem; + bottom: 0.25rem; + width: 1px; + content: var(--tw-content); + --tw-bg-opacity: 1; + background-color: rgb(229 231 235 / var(--tw-bg-opacity)); +} +.hextra-sidebar-container .hextra-sidebar-item-list:is(html[class~="dark"] *)::before { + content: var(--tw-content); + --tw-bg-opacity: 1; + background-color: rgb(38 38 38 / var(--tw-bg-opacity)); +} +.hextra-sidebar-container .hextra-sidebar-item-list:where([dir="ltr"], [dir="ltr"] *) { + margin-left: 0.75rem; + padding-left: 0.75rem; +} +.hextra-sidebar-container .hextra-sidebar-item-list:where([dir="ltr"], [dir="ltr"] *)::before { + content: var(--tw-content); + left: 0px; +} +.hextra-sidebar-container .hextra-sidebar-item-list:where([dir="rtl"], [dir="rtl"] *) { + margin-right: 0.75rem; + padding-right: 0.75rem; +} +.hextra-sidebar-container .hextra-sidebar-item-list:where([dir="rtl"], [dir="rtl"] *)::before { + content: var(--tw-content); + right: 0px; +} +.hextra-sidebar-container .hextra-sidebar-item-link { + display: flex; + cursor: pointer; + align-items: center; + justify-content: space-between; + gap: 0.5rem; + border-radius: 0.25rem; + padding-left: 0.5rem; + padding-right: 0.5rem; + padding-top: 0.375rem; + padding-bottom: 0.375rem; + font-size: .875rem; + transition-property: color, background-color, border-color, text-decoration-color, fill, stroke; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + transition-duration: 150ms; +} +.hextra-sidebar-container .hextra-sidebar-item-link.active { + --tw-bg-opacity: 1; + background-color: hsl(var(--primary-hue) var(--primary-saturation) calc(var(--primary-lightness) + calc(calc(100% - var(--primary-lightness)) / 50) * 44) / var(--tw-bg-opacity)); + font-weight: 600; + --tw-text-opacity: 1; + color: hsl(var(--primary-hue) var(--primary-saturation) calc(calc(var(--primary-lightness) / 50) * 32) / var(--tw-text-opacity)); +} +@media (prefers-contrast: more) { + + .hextra-sidebar-container .hextra-sidebar-item-link.active { + border-width: 1px; + --tw-border-opacity: 1; + border-color: hsl(var(--primary-hue) var(--primary-saturation) calc(calc(var(--primary-lightness) / 50) * 50) / var(--tw-border-opacity)); + } +} +.hextra-sidebar-container .hextra-sidebar-item-link.active:is(html[class~="dark"] *) { + background-color: hsl(var(--primary-hue) var(--primary-saturation) calc(var(--primary-lightness) + calc(calc(100% - var(--primary-lightness)) / 50) * 16) / 0.1); + --tw-text-opacity: 1; + color: hsl(var(--primary-hue) var(--primary-saturation) calc(calc(var(--primary-lightness) / 50) * 45) / var(--tw-text-opacity)); +} +@media (prefers-contrast: more) { + + .hextra-sidebar-container .hextra-sidebar-item-link.active:is(html[class~="dark"] *) { + --tw-border-opacity: 1; + border-color: hsl(var(--primary-hue) var(--primary-saturation) calc(calc(var(--primary-lightness) / 50) * 50) / var(--tw-border-opacity)); + } +} +.hextra-sidebar-container .hextra-sidebar-item-link.inactive { + --tw-text-opacity: 1; + color: rgb(107 114 128 / var(--tw-text-opacity)); +} +.hextra-sidebar-container .hextra-sidebar-item-link.inactive:hover { + --tw-bg-opacity: 1; + background-color: rgb(243 244 246 / var(--tw-bg-opacity)); + --tw-text-opacity: 1; + color: rgb(17 24 39 / var(--tw-text-opacity)); +} +@media (prefers-contrast: more) { + + .hextra-sidebar-container .hextra-sidebar-item-link.inactive { + border-width: 1px; + border-color: transparent; + --tw-text-opacity: 1; + color: rgb(17 24 39 / var(--tw-text-opacity)); + } + + .hextra-sidebar-container .hextra-sidebar-item-link.inactive:hover { + --tw-border-opacity: 1; + border-color: rgb(17 24 39 / var(--tw-border-opacity)); + } +} +.hextra-sidebar-container .hextra-sidebar-item-link.inactive:is(html[class~="dark"] *) { + --tw-text-opacity: 1; + color: rgb(163 163 163 / var(--tw-text-opacity)); +} +.hextra-sidebar-container .hextra-sidebar-item-link.inactive:hover:is(html[class~="dark"] *) { + background-color: hsl(var(--primary-hue) var(--primary-saturation) calc(var(--primary-lightness) + calc(calc(100% - var(--primary-lightness)) / 50) * 44) / 0.05); + --tw-text-opacity: 1; + color: rgb(249 250 251 / var(--tw-text-opacity)); +} +@media (prefers-contrast: more) { + + .hextra-sidebar-container .hextra-sidebar-item-link.inactive:is(html[class~="dark"] *) { + --tw-text-opacity: 1; + color: rgb(249 250 251 / var(--tw-text-opacity)); + } + + .hextra-sidebar-container .hextra-sidebar-item-link.inactive:hover:is(html[class~="dark"] *) { + --tw-border-opacity: 1; + border-color: rgb(249 250 251 / var(--tw-border-opacity)); + } +} nav .search-wrapper { display: none; } @@ -2689,11 +2802,6 @@ body:is(html[class~="dark"] *) { content: var(--tw-content); inset: 0px; } -.before\:hx-inset-y-1::before { - content: var(--tw-content); - top: 0.25rem; - bottom: 0.25rem; -} .before\:hx-mr-1::before { content: var(--tw-content); margin-right: 0.25rem; @@ -2702,37 +2810,16 @@ body:is(html[class~="dark"] *) { content: var(--tw-content); display: inline-block; } -.before\:hx-w-px::before { - content: var(--tw-content); - width: 1px; -} -.before\:hx-bg-gray-200::before { - content: var(--tw-content); - --tw-bg-opacity: 1; - background-color: rgb(229 231 235 / var(--tw-bg-opacity)); -} -.before\:hx-opacity-25::before { - content: var(--tw-content); - opacity: 0.25; -} .before\:hx-transition-transform::before { content: var(--tw-content); transition-property: transform; transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); transition-duration: 150ms; } -.before\:hx-content-\[\'\#\'\]::before { - --tw-content: '#'; - content: var(--tw-content); -} .before\:hx-content-\[\'\'\]::before { --tw-content: ''; content: var(--tw-content); } -.before\:hx-content-\[\\\"\\\"\]::before { - --tw-content: \"\"; - content: var(--tw-content); -} .first\:hx-mt-0:first-child { margin-top: 0px; } @@ -2911,15 +2998,6 @@ body:is(html[class~="dark"] *) { border-color: rgb(163 163 163 / var(--tw-border-opacity)); } - .contrast-more\:hx-border-primary-500 { - --tw-border-opacity: 1; - border-color: hsl(var(--primary-hue) var(--primary-saturation) calc(calc(var(--primary-lightness) / 50) * 50) / var(--tw-border-opacity)); - } - - .contrast-more\:hx-border-transparent { - border-color: transparent; - } - .contrast-more\:hx-font-bold { font-weight: 700; } @@ -2958,11 +3036,6 @@ body:is(html[class~="dark"] *) { --tw-shadow-colored: 0 0 #0000; box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); } - - .contrast-more\:hover\:hx-border-gray-900:hover { - --tw-border-opacity: 1; - border-color: rgb(17 24 39 / var(--tw-border-opacity)); - } } .dark\:hx-block:is(html[class~="dark"] *) { display: block; @@ -3048,9 +3121,6 @@ body:is(html[class~="dark"] *) { .dark\:hx-bg-primary-300\/10:is(html[class~="dark"] *) { background-color: hsl(var(--primary-hue) var(--primary-saturation) calc(var(--primary-lightness) + calc(calc(100% - var(--primary-lightness)) / 50) * 27) / 0.1); } -.dark\:hx-bg-primary-400\/10:is(html[class~="dark"] *) { - background-color: hsl(var(--primary-hue) var(--primary-saturation) calc(var(--primary-lightness) + calc(calc(100% - var(--primary-lightness)) / 50) * 16) / 0.1); -} .dark\:hx-bg-primary-600:is(html[class~="dark"] *) { --tw-bg-opacity: 1; background-color: hsl(var(--primary-hue) var(--primary-saturation) calc(calc(var(--primary-lightness) / 50) * 45) / var(--tw-bg-opacity)); @@ -3109,18 +3179,10 @@ body:is(html[class~="dark"] *) { --tw-text-opacity: 1; color: rgb(229 229 229 / var(--tw-text-opacity)); } -.dark\:hx-text-neutral-400:is(html[class~="dark"] *) { - --tw-text-opacity: 1; - color: rgb(163 163 163 / var(--tw-text-opacity)); -} .dark\:hx-text-orange-300:is(html[class~="dark"] *) { --tw-text-opacity: 1; color: rgb(253 186 116 / var(--tw-text-opacity)); } -.dark\:hx-text-primary-600:is(html[class~="dark"] *) { - --tw-text-opacity: 1; - color: hsl(var(--primary-hue) var(--primary-saturation) calc(calc(var(--primary-lightness) / 50) * 45) / var(--tw-text-opacity)); -} .dark\:hx-text-red-200:is(html[class~="dark"] *) { --tw-text-opacity: 1; color: rgb(254 202 202 / var(--tw-text-opacity)); @@ -3162,11 +3224,6 @@ body:is(html[class~="dark"] *) { --tw-text-opacity: 1; color: rgb(156 163 175 / var(--tw-text-opacity)); } -.dark\:before\:hx-bg-neutral-800:is(html[class~="dark"] *)::before { - content: var(--tw-content); - --tw-bg-opacity: 1; - background-color: rgb(38 38 38 / var(--tw-bg-opacity)); -} .dark\:before\:hx-invert:is(html[class~="dark"] *)::before { content: var(--tw-content); --tw-invert: invert(100%); @@ -3282,11 +3339,6 @@ body:is(html[class~="dark"] *) { border-color: rgb(163 163 163 / var(--tw-border-opacity)); } - .contrast-more\:dark\:hx-border-primary-500:is(html[class~="dark"] *) { - --tw-border-opacity: 1; - border-color: hsl(var(--primary-hue) var(--primary-saturation) calc(calc(var(--primary-lightness) / 50) * 50) / var(--tw-border-opacity)); - } - .dark\:contrast-more\:hx-border-neutral-400:is(html[class~="dark"] *) { --tw-border-opacity: 1; border-color: rgb(163 163 163 / var(--tw-border-opacity)); @@ -3322,11 +3374,6 @@ body:is(html[class~="dark"] *) { --tw-shadow-colored: 0 0 #0000; box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); } - - .contrast-more\:dark\:hover\:hx-border-gray-50:hover:is(html[class~="dark"] *) { - --tw-border-opacity: 1; - border-color: rgb(249 250 251 / var(--tw-border-opacity)); - } } @media not all and (min-width: 1280px) { @@ -3474,9 +3521,6 @@ body:is(html[class~="dark"] *) { .ltr\:hx-ml-1:where([dir="ltr"], [dir="ltr"] *) { margin-left: 0.25rem; } -.ltr\:hx-ml-3:where([dir="ltr"], [dir="ltr"] *) { - margin-left: 0.75rem; -} .ltr\:hx-ml-auto:where([dir="ltr"], [dir="ltr"] *) { margin-left: auto; } @@ -3526,10 +3570,6 @@ body:is(html[class~="dark"] *) { .ltr\:hx-text-right:where([dir="ltr"], [dir="ltr"] *) { text-align: right; } -.ltr\:before\:hx-left-0:where([dir="ltr"], [dir="ltr"] *)::before { - content: var(--tw-content); - left: 0px; -} @media (min-width: 768px) { .ltr\:md\:hx-left-auto:where([dir="ltr"], [dir="ltr"] *) { @@ -3551,9 +3591,6 @@ body:is(html[class~="dark"] *) { .rtl\:hx-mr-1:where([dir="rtl"], [dir="rtl"] *) { margin-right: 0.25rem; } -.rtl\:hx-mr-3:where([dir="rtl"], [dir="rtl"] *) { - margin-right: 0.75rem; -} .rtl\:hx-mr-auto:where([dir="rtl"], [dir="rtl"] *) { margin-right: auto; } @@ -3597,10 +3634,6 @@ body:is(html[class~="dark"] *) { .rtl\:hx-text-left:where([dir="rtl"], [dir="rtl"] *) { text-align: left; } -.rtl\:before\:hx-right-0:where([dir="rtl"], [dir="rtl"] *)::before { - content: var(--tw-content); - right: 0px; -} .rtl\:before\:hx-rotate-180:where([dir="rtl"], [dir="rtl"] *)::before { content: var(--tw-content); --tw-rotate: 180deg; diff --git a/assets/css/components/sidebar.css b/assets/css/components/sidebar.css index 394c8d63..7fe6c8c5 100644 --- a/assets/css/components/sidebar.css +++ b/assets/css/components/sidebar.css @@ -1,5 +1,5 @@ @media (max-width: 767px) { - .sidebar-container { + .hextra-sidebar-container { @apply hx-fixed hx-pt-[calc(var(--navbar-height))] hx-top-0 hx-w-full hx-bottom-0 hx-z-[15] hx-overscroll-contain hx-bg-white dark:hx-bg-dark; transition: transform 0.8s cubic-bezier(0.52, 0.16, 0.04, 1); will-change: transform, opacity; @@ -8,7 +8,7 @@ } } -.sidebar-container { +.hextra-sidebar-container { li > div { @apply hx-h-0; } @@ -18,4 +18,19 @@ li.open > a > span > svg > path { @apply hx-rotate-90; } + + .hextra-sidebar-item-list { + @apply hx-relative hx-flex hx-flex-col hx-gap-1 before:hx-absolute before:hx-inset-y-1 before:hx-w-px before:hx-bg-gray-200 ltr:hx-ml-3 ltr:hx-pl-3 ltr:before:hx-left-0 rtl:hx-mr-3 rtl:hx-pr-3 rtl:before:hx-right-0 dark:before:hx-bg-neutral-800; + } + + .hextra-sidebar-item-link { + @apply hx-flex hx-items-center hx-justify-between hx-gap-2 hx-cursor-pointer hx-rounded hx-px-2 hx-py-1.5 hx-text-sm hx-transition-colors; + + &.active { + @apply hx-bg-primary-100 hx-font-semibold hx-text-primary-800 contrast-more:hx-border contrast-more:hx-border-primary-500 dark:hx-bg-primary-400/10 dark:hx-text-primary-600 contrast-more:dark:hx-border-primary-500; + } + &.inactive { + @apply hx-text-gray-500 hover:hx-bg-gray-100 hover:hx-text-gray-900 contrast-more:hx-border contrast-more:hx-border-transparent contrast-more:hx-text-gray-900 contrast-more:hover:hx-border-gray-900 dark:hx-text-neutral-400 dark:hover:hx-bg-primary-100/5 dark:hover:hx-text-gray-50 contrast-more:dark:hx-text-gray-50 contrast-more:dark:hover:hx-border-gray-50; + } + } } diff --git a/assets/js/menu.js b/assets/js/menu.js index 9191b057..f27ed9a4 100644 --- a/assets/js/menu.js +++ b/assets/js/menu.js @@ -3,7 +3,7 @@ document.addEventListener('DOMContentLoaded', function () { const menu = document.querySelector('.hamburger-menu'); const overlay = document.querySelector('.mobile-menu-overlay'); - const sidebarContainer = document.querySelector('.sidebar-container'); + const sidebarContainer = document.querySelector('.hextra-sidebar-container'); // Initialize the overlay const overlayClasses = ['hx-fixed', 'hx-inset-0', 'hx-z-10', 'hx-bg-black/80', 'dark:hx-bg-black/60']; diff --git a/assets/js/sidebar.js b/assets/js/sidebar.js index 65f7b15f..b398d074 100644 --- a/assets/js/sidebar.js +++ b/assets/js/sidebar.js @@ -1,3 +1,12 @@ +/** + * Check if the element is visible. + * @param {Element} element Dom element + * @returns boolean + */ +function isVisible(element) { + return element.offsetWidth > 0 || element.offsetHeight > 0; +} + document.addEventListener("DOMContentLoaded", function () { scrollToActiveItem(); enableCollapsibles(); @@ -10,10 +19,43 @@ function enableCollapsibles() { e.preventDefault(); const list = button.parentElement.parentElement; if (list) { - list.classList.toggle("open") + list.classList.toggle("open"); } }); }); + + const isCached = "{{- site.Params.page.sidebar.cache | default false -}}" === "true"; + const currentPagePath = window.location.href; + + if (isCached) { + // find the current page in the sidebar and open the parent lists + const sidebar = document.querySelector(".hextra-sidebar-container"); + if (sidebar) { + // find a tags and compare href with current page path + const links = sidebar.querySelectorAll("a"); + links.forEach(function (link) { + const linkPath = link.href; + + if (currentPagePath === linkPath) { + // add active class to the link + link.classList.add("active"); + link.classList.remove("inactive"); + + if (!isVisible(link)) { + return; + } + // recursively open parent lists + let parent = link.parentElement; + while (parent && !parent.classList.contains("hextra-sidebar-container")) { + if (parent.tagName === "LI" && parent.classList.contains("hextra-sidebar-item")) { + parent.classList.add("open"); + } + parent = parent.parentElement; + } + } + }); + } + } } function scrollToActiveItem() { @@ -31,6 +73,6 @@ function scrollToActiveItem() { const yDistance = visibleActiveItem.getBoundingClientRect().top - sidebarScrollbar.getBoundingClientRect().top; sidebarScrollbar.scrollTo({ behavior: "instant", - top: yDistance - yOffset + top: yDistance - yOffset, }); } diff --git a/exampleSite/content/docs/guide/configuration.md b/exampleSite/content/docs/guide/configuration.md index 9c7a8f64..35625e30 100644 --- a/exampleSite/content/docs/guide/configuration.md +++ b/exampleSite/content/docs/guide/configuration.md @@ -87,7 +87,7 @@ params: ### Main Sidebar -For the main sidebar, it is automatically generated from the structure of the content directory. +By default, the main sidebar is automatically generated from the structure of the content directory. See the [Organize Files](/docs/guide/organize-files) page for more details. To exclude a single page from the left sidebar, set the `sidebar.exclude` parameter in the front matter of the page: @@ -119,6 +119,33 @@ menu: weight: 3 ``` +### Sidebar from Data + +Alternatively, you can define the sidebar structure in the `data` directory. This gives you more flexibility to define sidebar differently than your content structure. To enable this, set the `params.sidebar.source` parameter in the config file to `data`: + +```yaml {filename="hugo.yaml"} +params: + sidebar: + source: data +``` + +To define the sidebar data, create a file named `sidebar.yaml` in the `data` directory. + +```yaml {filename="data/sidebar.yaml"} +docs: + - title: Documentation + link: /docs/ + - title: Guide + link: /docs/guide/ + open: false + items: + - title: Configuration + link: /docs/guide/configuration/ + # ... +``` + +If your site is multilingual, you can define the sidebar data for each language, for example in `data/en/sidebar.yaml` file. + ## Right Sidebar ### Table of Contents diff --git a/exampleSite/data/en/sidebar.yaml b/exampleSite/data/en/sidebar.yaml new file mode 100644 index 00000000..132f347b --- /dev/null +++ b/exampleSite/data/en/sidebar.yaml @@ -0,0 +1,49 @@ +docs: + - title: Documentation + link: /docs/ + - title: Getting Started + link: /docs/getting-started/ + - title: Guide + link: /docs/guide/ + open: false + items: + - title: Organize Files + link: /docs/guide/organize-files/ + - title: Configuration + link: /docs/guide/configuration/ + - title: Markdown + link: /docs/guide/markdown/ + - title: Syntax Highlighting + link: /docs/guide/syntax-highlighting/ + - title: LaTeX + link: /docs/guide/latex/ + - title: Diagrams + link: /docs/guide/diagrams/ + - title: Shortcodes + link: /docs/guide/shortcodes/ + items: + - title: Callout + link: /docs/guide/shortcodes/callout/ + - title: Cards + link: /docs/guide/shortcodes/cards/ + - title: Details + link: /docs/guide/shortcodes/details/ + - title: FileTree + link: /docs/guide/shortcodes/filetree/ + - title: Icon + link: /docs/guide/shortcodes/icon/ + - title: Steps + link: /docs/guide/shortcodes/steps/ + - title: Tabs + link: /docs/guide/shortcodes/tabs/ + - title: Deploy Site + link: /docs/guide/deploy-site/ + - title: Advanced + link: /docs/advanced/ + items: + - title: Multi-language + link: /docs/advanced/multi-language/ + - title: Comments + link: /docs/advanced/comments/ + - title: Customization + link: /docs/advanced/customization/ diff --git a/exampleSite/hugo.yaml b/exampleSite/hugo.yaml index c8c3301e..ff8ea4f3 100644 --- a/exampleSite/hugo.yaml +++ b/exampleSite/hugo.yaml @@ -113,6 +113,10 @@ params: # full (100%), wide (90rem), normal (1280px) width: normal + # TODO: move one level up + sidebar: + source: data + theme: # light | dark | system default: system diff --git a/exampleSite/hugo_stats.json b/exampleSite/hugo_stats.json index 65d58c9f..68656ebc 100644 --- a/exampleSite/hugo_stats.json +++ b/exampleSite/hugo_stats.json @@ -69,46 +69,36 @@ "[hyphens:auto]", "[transition:background-color_1.5s_ease]", "[word-break:break-word]", + "active", "active:hx-bg-gray-400/20", "active:hx-opacity-50", "active:hx-shadow-gray-200", "active:hx-shadow-sm", "before:hx-absolute", "before:hx-bg-glass-gradient", - "before:hx-bg-gray-200", - "before:hx-content-[\"\"]", - "before:hx-content-['#']", "before:hx-content-['']", "before:hx-inline-block", "before:hx-inset-0", - "before:hx-inset-y-1", "before:hx-mr-1", - "before:hx-opacity-25", "before:hx-pointer-events-none", "before:hx-transition-transform", - "before:hx-w-px", "chroma", "content", - "contrast-more:dark:hover:hx-border-gray-50", "contrast-more:dark:hx-border-current", "contrast-more:dark:hx-border-gray-50", "contrast-more:dark:hx-border-neutral-400", - "contrast-more:dark:hx-border-primary-500", "contrast-more:dark:hx-shadow-[0_0_0_1px_#fff]", "contrast-more:dark:hx-shadow-none", "contrast-more:dark:hx-text-current", "contrast-more:dark:hx-text-gray-100", "contrast-more:dark:hx-text-gray-300", "contrast-more:dark:hx-text-gray-50", - "contrast-more:hover:hx-border-gray-900", "contrast-more:hx-border", "contrast-more:hx-border-current", "contrast-more:hx-border-gray-800", "contrast-more:hx-border-gray-900", "contrast-more:hx-border-neutral-400", - "contrast-more:hx-border-primary-500", "contrast-more:hx-border-t", - "contrast-more:hx-border-transparent", "contrast-more:hx-font-bold", "contrast-more:hx-shadow-[0_0_0_1px_#000]", "contrast-more:hx-shadow-none", @@ -118,7 +108,6 @@ "contrast-more:hx-text-gray-900", "contrast-more:hx-underline", "copy-icon", - "dark:before:hx-bg-neutral-800", "dark:before:hx-invert", "dark:contrast-more:hx-border-neutral-400", "dark:focus:hx-bg-dark", @@ -153,7 +142,6 @@ "dark:hx-bg-neutral-900", "dark:hx-bg-orange-400/20", "dark:hx-bg-primary-300/10", - "dark:hx-bg-primary-400/10", "dark:hx-bg-primary-600", "dark:hx-bg-red-900/30", "dark:hx-bg-yellow-700/30", @@ -187,9 +175,7 @@ "dark:hx-text-green-200", "dark:hx-text-indigo-200", "dark:hx-text-neutral-200", - "dark:hx-text-neutral-400", "dark:hx-text-orange-300", - "dark:hx-text-primary-600", "dark:hx-text-red-200", "dark:hx-text-slate-100", "dark:hx-text-yellow-200", @@ -242,6 +228,10 @@ "hextra-pdf", "hextra-scrollbar", "hextra-sidebar-collapsible-button", + "hextra-sidebar-container", + "hextra-sidebar-item", + "hextra-sidebar-item-link", + "hextra-sidebar-item-list", "hextra-tabs-panel", "hextra-tabs-toggle", "hextra-toc", @@ -283,7 +273,6 @@ "hx-bg-indigo-100", "hx-bg-neutral-50", "hx-bg-orange-50", - "hx-bg-primary-100", "hx-bg-primary-400", "hx-bg-primary-600", "hx-bg-primary-700/5", @@ -355,7 +344,6 @@ "hx-inset-y-0", "hx-items-center", "hx-items-start", - "hx-justify-between", "hx-justify-center", "hx-justify-end", "hx-justify-items-start", @@ -504,7 +492,6 @@ "hx-text-left", "hx-text-lg", "hx-text-orange-800", - "hx-text-primary-800", "hx-text-red-900", "hx-text-slate-900", "hx-text-sm", @@ -542,16 +529,15 @@ "hx-z-20", "hx-z-[-1]", "icon", + "inactive", "language-options", "language-switcher", "last-of-type:hx-mb-0", "lntable", "lntd", - "ltr:before:hx-left-0", "ltr:hx--mr-4", "ltr:hx-border-l", "ltr:hx-ml-1", - "ltr:hx-ml-3", "ltr:hx-ml-auto", "ltr:hx-mr-auto", "ltr:hx-pl-12", @@ -603,7 +589,6 @@ "print:hx-bg-transparent", "print:hx-hidden", "rtl:-hx-rotate-180", - "rtl:before:hx-right-0", "rtl:before:hx-rotate-180", "rtl:hx--ml-4", "rtl:hx-border-r", @@ -611,7 +596,6 @@ "rtl:hx-left-3", "rtl:hx-ml-auto", "rtl:hx-mr-1", - "rtl:hx-mr-3", "rtl:hx-mr-auto", "rtl:hx-pl-2", "rtl:hx-pl-4", @@ -628,8 +612,6 @@ "search-input", "search-results", "search-wrapper", - "sidebar-active-item", - "sidebar-container", "sm:hx-block", "sm:hx-flex", "sm:hx-items-start", diff --git a/layouts/_default/list.html b/layouts/_default/list.html index 72c48335..83280996 100644 --- a/layouts/_default/list.html +++ b/layouts/_default/list.html @@ -1,6 +1,8 @@ {{ define "main" }}
- {{ partial "sidebar.html" (dict "context" .) }} + {{- if not (in (slice "taxonomy" "term") .Kind) -}} + {{- partial "sidebar.html" (dict "context" .) -}} + {{- end -}} {{ partial "toc.html" . }}
diff --git a/layouts/partials/components/sidebar/bottom.html b/layouts/partials/components/sidebar/bottom.html new file mode 100644 index 00000000..40871bd3 --- /dev/null +++ b/layouts/partials/components/sidebar/bottom.html @@ -0,0 +1,12 @@ +{{- range site.Menus.sidebar }} + {{- $name := or (T .Identifier) .Name }} + {{- if eq .Params.type "separator" }} +
  • + {{ $name }} +
  • + {{- else }} +
  • + {{- partial "components/sidebar/item-link" (dict "active" false "title" $name "link" (.URL | relLangURL)) -}} +
  • + {{- end }} +{{- end -}} diff --git a/layouts/partials/components/sidebar/collapsible-button.html b/layouts/partials/components/sidebar/collapsible-button.html new file mode 100644 index 00000000..3ba74f3a --- /dev/null +++ b/layouts/partials/components/sidebar/collapsible-button.html @@ -0,0 +1,5 @@ + + + + + diff --git a/layouts/partials/components/sidebar/generate-mobile-data.html b/layouts/partials/components/sidebar/generate-mobile-data.html new file mode 100644 index 00000000..7b518f40 --- /dev/null +++ b/layouts/partials/components/sidebar/generate-mobile-data.html @@ -0,0 +1,35 @@ +{{/* Generate mobile navigation data based on main menu */}} + +{{- $context := . -}} + +{{- $data := slice -}} + +{{- range .Site.Menus.main -}} + {{- if not (eq .Params.type "search") -}} + {{- $title := or (T .Identifier) .Name -}} + {{- $link := .URL -}} + {{- $external := strings.HasPrefix $link "http" -}} + {{- with .PageRef -}} + {{- if hasPrefix . "/" -}} + {{- $link = relLangURL (strings.TrimPrefix "/" .) -}} + {{- end -}} + {{- end -}} + + {{- with .Page -}} + {{- $page := . -}} + + {{- if and $page.IsSection (eq $page.Type "docs") -}} + {{- $page = (partial "utils/translated-page" (dict "page" $page "lang" site.Language.LanguageCode)) -}} + {{- $sectionData := (partial "components/sidebar/generate-section-data" $page) | unmarshal -}} + {{- $data = $data | append (dict "title" $title "link" $link "items" $sectionData) -}} + {{- else -}} + {{- $data = $data | append (dict "title" $title "link" $link) -}} + {{- end -}} + {{- else -}} + {{/* TODO: handle other cases like external links */}} + {{- $data = $data | append (dict "title" $title "link" $link) -}} + {{- end -}} + {{- end -}} +{{- end -}} + +{{- return ($data | jsonify (dict "noHTMLEscape" true)) -}} diff --git a/layouts/partials/components/sidebar/generate-section-data.html b/layouts/partials/components/sidebar/generate-section-data.html new file mode 100644 index 00000000..a44dd0cf --- /dev/null +++ b/layouts/partials/components/sidebar/generate-section-data.html @@ -0,0 +1,51 @@ +{{- $context := . -}} + +{{- $pages := union .RegularPages .Sections -}} +{{- $pages = where $pages "Params.sidebar.exclude" "!=" true -}} + +{{- $data := slice -}} + +{{- range $pages.ByWeight -}} + {{ $structure := (partial "sidebar/section-walk" .) | unmarshal -}} + {{ $data = $data | append $structure -}} +{{ end -}} + +{{- define "partials/sidebar/section-walk" -}} + {{- with . -}} + { + "title": "{{ partial "utils/title" . }}", + "link": "{{ .RelPermalink }}", + "toc": {{ partial "sidebar/section-page-toc" . }}, + "open": {{ .Params.sidebar.open | default false }} + {{- if .IsSection }}, + "items": [ + {{ $pages := union .RegularPages .Sections -}} + {{ $pages = where $pages "Params.sidebar.exclude" "!=" true -}} + {{ range $index, $page := $pages.ByWeight -}} + {{ partial "sidebar/section-walk" . }}{{ if not (ge $index (sub (len $pages) 1)) }},{{ end -}} + {{ end -}} + ] + {{ end -}} + } + {{- end }} +{{- end -}} + +{{- define "partials/sidebar/section-page-toc" -}} + {{/* Get level 2 headings list used mainly for mobile navigation */}} + [ + {{- with .Fragments.Headings -}} + {{/* Loop over level 1 headings */}} + {{- range . }} + {{- with .Headings }} + {{ $headings := . }} + {{- range $index, $heading := $headings }} + {{ $heading.Title | jsonify (dict "noHTMLEscape" true) }} + {{- if not (ge $index (sub (len $headings) 1)) }},{{ end -}} + {{ end -}} + {{- end -}} + {{ end -}} + {{- end -}} + ] +{{- end -}} + +{{ return ($data | jsonify (dict "noHTMLEscape" true)) }} diff --git a/layouts/partials/components/sidebar/get-section-data.html b/layouts/partials/components/sidebar/get-section-data.html new file mode 100644 index 00000000..8b073a89 --- /dev/null +++ b/layouts/partials/components/sidebar/get-section-data.html @@ -0,0 +1,20 @@ +{{/* Get section sidebar config from Hugo `data` directory + + If the site is multilingual, the sidebar data is stored in a language-specific + directory. For example, the English sidebar data is stored in `data/en/sidebar.yaml`. +*/}} +{{ $data := "" }} +{{ $section := .Section | default "index" }} +{{ $filename := "sidebar" }} + +{{ if hugo.IsMultilingual }} + {{ with (index site.Data site.Language.Lang $filename $section) }} + {{ $data = . }} + {{ end }} +{{ else }} + {{ with (index site.Data $filename $section) }} + {{ $data = . }} + {{ end }} +{{ end }} + +{{ return $data }} diff --git a/layouts/partials/components/sidebar/item-link.html b/layouts/partials/components/sidebar/item-link.html new file mode 100644 index 00000000..c60a52e1 --- /dev/null +++ b/layouts/partials/components/sidebar/item-link.html @@ -0,0 +1,18 @@ +{{- $external := strings.HasPrefix .link "http" -}} + +{{- $activeClass := cond (.active) "active" "inactive" -}} + + + + {{- .title -}} + {{- with .context }} + {{- if or .RegularPages .Sections }} + {{- partialCached "components/sidebar/collapsible-button" . }} + {{- end }} + {{ end -}} + {{- with .items }}{{- partialCached "components/sidebar/collapsible-button" site.Home }}{{ end -}} + diff --git a/layouts/partials/components/sidebar/render-data.html b/layouts/partials/components/sidebar/render-data.html new file mode 100644 index 00000000..41750cd3 --- /dev/null +++ b/layouts/partials/components/sidebar/render-data.html @@ -0,0 +1,16 @@ +{{- $page := .page -}} +{{- $pageLink := $page.RelPermalink -}} +{{- $cached := .cached | default false }} + +{{- range .data -}} + {{- $active := and (not $cached) (or (eq $pageLink .link) (eq (strings.TrimSuffix "/" $pageLink) .link)) -}} + {{- $containsPage := hasPrefix $pageLink .link -}} + {{- $shouldOpen := or (.open) $containsPage $active | default false -}} + +
  • + {{- partial "components/sidebar/item-link" (dict "active" $active "title" .title "link" .link "items" .items) -}} + {{- if .items -}} + {{- partial "components/sidebar/render-items" (dict "items" .items "link" $pageLink "cached" $cached) -}} + {{- end -}} +
  • +{{ end }} diff --git a/layouts/partials/components/sidebar/render-items.html b/layouts/partials/components/sidebar/render-items.html new file mode 100644 index 00000000..b43f5ce3 --- /dev/null +++ b/layouts/partials/components/sidebar/render-items.html @@ -0,0 +1,21 @@ +{{- $items := .items -}} +{{- $pageLink := .link -}} +{{- $cached := .cached | default false }} + + +
    +
      + {{- range $items }} + {{- $active := and (not $cached) (or (eq $pageLink .link) (eq (strings.TrimSuffix "/" $pageLink) .link)) -}} + {{- $containsPage := hasPrefix $pageLink .link -}} + {{- $shouldOpen := or (.open) $containsPage $active | default false -}} + +
    • + {{- partial "components/sidebar/item-link" (dict "active" $active "title" .title "link" .link "items" .items) -}} + {{- if .items -}} + {{- partial "components/sidebar/render-items" (dict "items" .items "link" $pageLink "cached" $cached) -}} + {{- end -}} +
    • + {{- end -}} +
    +
    diff --git a/layouts/partials/scripts.html b/layouts/partials/scripts.html index 03d5926c..f6a97a08 100644 --- a/layouts/partials/scripts.html +++ b/layouts/partials/scripts.html @@ -4,7 +4,7 @@ {{- $jsLang := resources.Get "js/lang.js" -}} {{- $jsCodeCopy := resources.Get "js/code-copy.js" -}} {{- $jsFileTree := resources.Get "js/filetree.js" -}} -{{- $jsSidebar := resources.Get "js/sidebar.js" -}} +{{- $jsSidebar := resources.Get "js/sidebar.js" | resources.ExecuteAsTemplate "sidebar.js" . -}} {{- $jsBackToTop := resources.Get "js/back-to-top.js" -}} {{- $scripts := slice $jsTheme $jsMenu $jsCodeCopy $jsTabs $jsLang $jsFileTree $jsSidebar $jsBackToTop | resources.Concat "js/main.js" -}} diff --git a/layouts/partials/sidebar.html b/layouts/partials/sidebar.html index 6698f106..dcd9eb83 100644 --- a/layouts/partials/sidebar.html +++ b/layouts/partials/sidebar.html @@ -3,48 +3,83 @@ {{- $disableSidebar := .disableSidebar | default false -}} {{- $displayPlaceholder := .displayPlaceholder | default false -}} +{{/* EXPERIMENTAL - allow hiding sidebar on a per-page basis */}} +{{- if $context.Params.sidebar.hide -}} + {{- $disableSidebar = true -}} + {{- $displayPlaceholder = true -}} +{{- end -}} + {{- $sidebarClass := cond $disableSidebar (cond $displayPlaceholder "md:hx-hidden xl:hx-block" "md:hx-hidden") "md:hx-sticky" -}} {{- $navRoot := cond (eq site.Home.Type "docs") site.Home $context.FirstSection -}} {{- $pageURL := $context.RelPermalink -}} -{{/* EXPERIMENTAL */}} -{{- if .context.Params.sidebar.hide -}} - {{- $disableSidebar = true -}} - {{- $displayPlaceholder = true -}} +{{- $data := slice -}} +{{- $dataMobile := (partialCached "components/sidebar/generate-mobile-data" site.Home site.Home) | unmarshal -}} + +{{- if (eq site.Params.page.sidebar.source "data") -}} + {{/* Get sidebar data from Hugo `data` directory */}} + {{- $data = partialCached "components/sidebar/get-section-data" $context $context.Section -}} +{{- else -}} + {{/* Generate and cache sidebar data in memory */}} + {{- $data = (partialCached "components/sidebar/generate-section-data" $navRoot $navRoot) | unmarshal -}} {{- end -}} +{{/* Cache rendered sidebar */}} +{{- $shouldCache := site.Params.page.sidebar.cache | default false -}}
    - - -{{- define "sidebar-main" -}} - {{ template "sidebar-tree" (dict "context" .context "level" 0 "page" .page "pageURL" .pageURL "toc" (.toc | default false)) }} -{{- end -}} - -{{- define "sidebar-tree" -}} - {{- if ge .level 4 -}} - {{- return -}} - {{- end -}} - - {{- $context := .context -}} - {{- $page := .page }} - {{- $pageURL := .page.RelPermalink -}} - {{- $level := .level -}} - {{- $toc := .toc | default false -}} - - {{- with $items := union .context.RegularPages .context.Sections -}} - {{- $items = where $items "Params.sidebar.exclude" "!=" true -}} - {{- if eq $level 0 -}} - {{- range $items.ByWeight }} - {{- if .Params.sidebar.separator -}} -
  • - {{ partial "utils/title" . }} -
  • - {{- else -}} - {{- $active := eq $pageURL .RelPermalink -}} - {{- $shouldOpen := or (.Params.sidebar.open) (.IsAncestor $page) $active | default true }} -
  • - {{- $linkTitle := partial "utils/title" . -}} - {{- template "sidebar-item-link" dict "context" . "active" $active "title" $linkTitle "link" .RelPermalink -}} - {{- if and $toc $active -}} - {{- template "sidebar-toc" dict "page" . -}} - {{- end -}} - {{- template "sidebar-tree" dict "context" . "page" $page "pageURL" $pageURL "level" (add $level 1) "toc" $toc -}} -
  • - {{- end -}} - {{- end -}} - {{- else -}} -
    -
      - {{- range $items.ByWeight }} - {{- $active := eq $pageURL .RelPermalink -}} - {{- $shouldOpen := or (.Params.sidebar.open) (.IsAncestor $page) $active | default true }} - {{- $linkTitle := partial "utils/title" . -}} -
    • - {{- template "sidebar-item-link" dict "context" . "active" $active "title" $linkTitle "link" .RelPermalink -}} - {{- if and $toc $active -}} - {{ template "sidebar-toc" dict "page" . }} - {{- end }} - {{ template "sidebar-tree" dict "context" . "page" $page "pageURL" $pageURL "level" (add $level 1) "toc" $toc }} -
    • - {{- end -}} -
    -
    - {{- end -}} - {{- end }} -{{- end -}} - -{{- define "sidebar-toc" -}} - {{ $page := .page }} - {{ with $page.Fragments.Headings }} -
      - {{- range . }} - {{- with .Headings }} - {{- range . -}} -
    • - - {{- .Title -}} - -
    • - {{ end -}} - {{ end -}} - {{ end -}} -
    - {{ end }} -{{- end -}} - -{{- define "sidebar-footer" -}} - {{- range site.Menus.sidebar -}} - {{- $name := or (T .Identifier) .Name -}} - {{ if eq .Params.type "separator" }} -
  • - {{ $name }} -
  • - {{ else }} -
  • {{ template "sidebar-item-link" dict "active" false "title" $name "link" (.URL | relLangURL) }}
  • - {{ end }} - {{- end -}} -{{- end -}} - -{{- define "sidebar-item-link" -}} - {{- $external := strings.HasPrefix .link "http" -}} - {{- $open := .open | default true -}} - - {{- .title -}} - {{- with .context }} - {{- if or .RegularPages .Sections }} - - {{- template "sidebar-collapsible-button" -}} - - {{- end }} - {{ end -}} - -{{- end -}} - -{{- define "sidebar-collapsible-button" -}} - {{- end -}} diff --git a/layouts/partials/utils/translated-page.html b/layouts/partials/utils/translated-page.html new file mode 100644 index 00000000..42da86d0 --- /dev/null +++ b/layouts/partials/utils/translated-page.html @@ -0,0 +1,17 @@ +{{/* + Utility to retrieve a translated page given a page and a language code. + + If the page is not translated, it returns the original page. +*/}} +{{- $page := .page -}} +{{- $lang := .lang -}} + +{{- if $page.IsTranslated -}} + {{- range $page.AllTranslations -}} + {{- if eq .Language.LanguageCode $lang -}} + {{- $page = . -}} + {{- end -}} + {{- end -}} +{{- end -}} + +{{- return $page -}}