1import { Controller } from "@hotwired/stimulus" 2 3export default class extends Controller { 4 static targets = ["sidebar", "overlay"] 5 6 connect() { 7 // Initialize sidebar state based on screen size 8 this.initializeSidebar() 9 window.addEventListener("resize", this.handleResize.bind(this)) 10 this.setupNavigationLinks() 11 this.setupTurboEvents() 12 13 this.boundUpdateActiveLink = this.updateActiveLink.bind(this) 14 document.addEventListener("turbo:frame-load", this.boundUpdateActiveLink) 15 } 16 17 disconnect() { 18 window.removeEventListener("resize", this.handleResize.bind(this)) 19 document.removeEventListener("turbo:frame-load", this.boundUpdateActiveLink) 20 } 21 22 updateActiveLink(event) { 23 const url = new URL(window.location.href) 24 const currentPath = url.pathname 25 const currentParams = url.searchParams 26 27 const navLinks = this.sidebarTarget.querySelectorAll('nav a[href]') 28 navLinks.forEach(link => { 29 link.classList.remove("active"); 30 const linkUrl = new URL(link.href) 31 const linkPath = linkUrl.pathname 32 const linkParams = linkUrl.searchParams 33 34 // Check if this is a snippet section link 35 if (linkPath.includes('/admin/snippets') && linkParams.has('section')) { 36 // For snippet sections, mark as active if the section matches, regardless of subsection 37 const isActive = linkPath === currentPath && 38 linkParams.get('section') === currentParams.get('section') 39 link.classList.toggle("active", isActive) 40 } else { 41 // For other links, use exact matching
42 const linkFullPath = linkUrl.pathname + linkUrl.search 43 const currentFullPath = url.pathname + url.search 44 link.classList.toggle("active", linkFullPath === currentFullPath) 45 } 46 }) 47 } 48 49 initializeSidebar() { 50 if (window.innerWidth < 768) { 51 // Mobile: hide sidebar by default 52 this.sidebarTarget.classList.add("-translate-x-full") 53 this.overlayTarget.style.display = "none" 54 } else { 55 // Desktop: show sidebar by default 56 this.sidebarTarget.classList.remove("-translate-x-full") 57 this.overlayTarget.style.display = "none" 58 } 59 } 60 61 toggleSidebar() { 62 if (this.isSidebarOpen()) { 63 this.closeSidebar() 64 } else { 65 this.openSidebar() 66 } 67 } 68 69 openSidebar() { 70 this.sidebarTarget.classList.remove("-translate-x-full") 71 this.overlayTarget.style.display = "block" 72 document.body.style.overflow = "hidden" 73 } 74 75 closeSidebar() { 76 this.sidebarTarget.classList.add("-translate-x-full") 77 this.overlayTarget.style.display = "none" 78 document.body.style.overflow = "auto" 79 } 80 81 handleResize() { 82 if (window.innerWidth >= 768) { 83 // Desktop: show sidebar, hide overlay 84 this.sidebarTarget.classList.remove("-translate-x-full") 85 this.overlayTarget.style.display = "none" 86 document.body.style.overflow = "auto" 87 } else { 88 // Mobile: hide sidebar by default 89 this.sidebarTarget.classList.add("-translate-x-full") 90 this.overlayTarget.style.display = "none" 91 document.body.style.overflow = "auto" 92 } 93 } 94 95 isSidebarOpen() { 96 return !this.sidebarTarget.classList.contains("-translate-x-full") 97 } 98 99 setupNavigationLinks() { 100 // Find all navigation links in the sidebar 101 const navLinks = this.sidebarTarget.querySelectorAll('nav a[href]') 102 103 navLinks.forEach(link => { 104 link.addEventListener('click', (event) => { 105 // Only add delay on mobile when sidebar is open 106 if (window.innerWidth < 768 && this.isSidebarOpen()) { 107 // Add a brief delay before closing sidebar 108 setTimeout(() => { 109 this.closeSidebar() 110 }, 200) 111 } 112 }) 113 }) 114 } 115 116 setupTurboEvents() { 117 // No longer needed since sidebar persists with data-turbo-permanent 118 } 119}
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.