@@ -40,31 +40,43 @@ export class Navbar extends LitElement {
4040 @property ( )
4141 locationPathname : string = location . pathname ;
4242
43-
4443 /**
4544 * Header theme (light/dark/auto)
4645 */
4746 @property ( )
4847 theme = 'light' ;
4948
49+ /**
50+ * Supported documentation versions with labels
51+ * @example [{version: '2.504.x', label: 'Stable'}, {version: 'latest', label: 'Nightly'}]
52+ */
53+ @property ( { type : Array } )
54+ docVersions : Array < { version : string , label : string } > = [
55+ { version : '2.504.x' , label : 'Stable' }
56+ ] ;
57+
58+ /**
59+ * Currently active documentation version
60+ */
61+ @property ( { type : String } )
62+ currentDocVersion = '2.504.x' ;
63+
5064 /**
5165 * Keeps track of what menu is opened.
52- *
53- * Never to be set externally, though storybook shows it.
5466 * @private
5567 */
5668 @state ( )
5769 private visibleMenu = - 1 ;
5870
5971 /**
6072 * Keeps track if the collapsed (mobile) menu is shown or not
61- *
62- * Never to be set externally, though storybook shows it.
6373 * @private
6474 */
6575 @state ( )
6676 private menuToggled = false ;
6777
78+ private isDocsSite = window . location . hostname === 'docs.jenkins.io' ;
79+
6880 constructor ( ) {
6981 super ( ) ;
7082 this . handleDocumentClick = this . handleDocumentClick . bind ( this ) ;
@@ -73,6 +85,11 @@ export class Navbar extends LitElement {
7385 override connectedCallback ( ) {
7486 super . connectedCallback ( ) ;
7587 document . addEventListener ( 'click' , this . handleDocumentClick ) ;
88+
89+ // Initialize current version from first available if not set
90+ if ( ! this . currentDocVersion && this . docVersions . length > 0 ) {
91+ this . currentDocVersion = this . docVersions [ 0 ] . version ;
92+ }
7693 }
7794
7895 override disconnectedCallback ( ) {
@@ -83,12 +100,10 @@ export class Navbar extends LitElement {
83100 handleDocumentClick ( ) {
84101 this . visibleMenu = - 1 ;
85102 }
86- private isDocsSite = window . location . hostname === 'docs.jenkins.io' ;
87- private docsVersion = '2.504.x' ;
88103
89104 private getDocsUrl ( originalPath : string ) : string {
90-
91- const cleanPath = originalPath . replace ( / ^ h t t p s ? : \/ \/ [ ^ / ] + / , '' ) . split ( / [ # ? ] / ) [ 0 ] ;
105+ const [ cleanPath , query , hash ] = originalPath . replace ( / ^ h t t p s ? : \/ \/ [ ^ \/ ] + / , '' ) . split ( / [ ? # ] / ) ;
106+
92107 const docMappings : Record < string , { path : string , versioned : boolean } > = {
93108 // User Guide sections (versioned)
94109 '/doc/book' : { path : '/user-docs' , versioned : true } ,
@@ -142,23 +157,38 @@ export class Navbar extends LitElement {
142157 if ( versioned ) {
143158 const pathParts = newPath . split ( '/' ) . filter ( part => part !== '' ) ;
144159 if ( pathParts . length >= 1 ) {
145- pathParts . splice ( 1 , 0 , this . docsVersion ) ;
160+ pathParts . splice ( 1 , 0 , this . currentDocVersion ) ;
146161 newPath = '/' + pathParts . join ( '/' ) ;
147162 } else {
148- newPath = `/${ this . docsVersion } ` ;
163+ newPath = `/${ this . currentDocVersion } ` ;
149164 }
150165 }
151166
152167 if ( ! newPath . endsWith ( 'index.html' ) ) {
153168 newPath = newPath . replace ( / ( \/ ) ? $ / , '/' ) + 'index.html' ;
154169 }
155-
156- return `https://docs.jenkins.io${ newPath } ` ;
170+
171+ // Reconstruct URL with query/hash if they existed
172+ let finalUrl = `https://docs.jenkins.io${ newPath } ` ;
173+ if ( query ) finalUrl += `?${ query } ` ;
174+ if ( hash ) finalUrl += `#${ hash } ` ;
175+ return finalUrl ;
157176 }
158177
159- return this . isDocsSite
160- ? `https://docs.jenkins.io${ cleanPath } `
161- : `https://www.jenkins.io${ cleanPath } ` ;
178+ // For all other paths, use standard behavior
179+ const baseUrl = this . isDocsSite ? 'https://docs.jenkins.io' : 'https://www.jenkins.io' ;
180+ return `${ baseUrl } ${ cleanPath } ` ;
181+ }
182+
183+ private _handleVersionChange ( e : Event ) {
184+ const newVersion = ( e . target as HTMLSelectElement ) . value ;
185+ if ( newVersion !== this . currentDocVersion ) {
186+ this . currentDocVersion = newVersion ;
187+ // Dispatch event to notify parent of version change
188+ this . dispatchEvent ( new CustomEvent ( 'version-changed' , {
189+ detail : { version : this . currentDocVersion }
190+ } ) ) ;
191+ }
162192 }
163193
164194 override render ( ) {
@@ -168,117 +198,147 @@ export class Navbar extends LitElement {
168198 { label : msg ( "Tekton" ) , link : "https://tekton.dev/" } ,
169199 { label : msg ( "Spinnaker" ) , link : "https://www.spinnaker.io/" } ,
170200 ] ;
201+
171202 const menuItems = [
172203 { label : msg ( "Blog" ) , link : "/blog/" } ,
173204 { label : msg ( "Success Stories" ) , link : "https://stories.jenkins.io/" } ,
174205 { label : msg ( "Contributor Spotlight" ) , link : "https://contributors.jenkins.io/" } ,
175206 {
176207 label : msg ( "Documentation" ) , link : [
177208 {
178- label : msg ( "User Guide" ) , link : "/doc/book" , header : true
209+ label : msg ( "User Guide" ) ,
210+ link : this . getDocsUrl ( "/doc/book" ) ,
211+ header : true
179212 } ,
180- { label : "- " + msg ( "Installing Jenkins" ) , link : "/doc/book/installing/" } ,
181- { label : "- " + msg ( "Jenkins Pipeline" ) , link : "/doc/book/pipeline/" } ,
182- { label : "- " + msg ( "Managing Jenkins" ) , link : "/doc/book/managing/" } ,
183- { label : "- " + msg ( "Securing Jenkins" ) , link : "/doc/book/security/" } ,
184- { label : "- " + msg ( "System Administration" ) , link : "/doc/book/system-administration/" } ,
185- { label : "- " + msg ( "Troubleshooting Jenkins" ) , link : "/doc/book/troubleshooting/" } ,
186- { label : "- " + msg ( "Terms and Definitions" ) , link : "/doc/book/glossary/" } ,
187- { label : msg ( "Solution Pages" ) , link : "/solutions" , header : true } ,
213+ { label : "- " + msg ( "Installing Jenkins" ) , link : this . getDocsUrl ( "/doc/book/installing/" ) } ,
214+ { label : "- " + msg ( "Jenkins Pipeline" ) , link : this . getDocsUrl ( "/doc/book/pipeline/" ) } ,
215+ { label : "- " + msg ( "Managing Jenkins" ) , link : this . getDocsUrl ( "/doc/book/managing/" ) } ,
216+ { label : "- " + msg ( "Securing Jenkins" ) , link : this . getDocsUrl ( "/doc/book/security/" ) } ,
217+ { label : "- " + msg ( "System Administration" ) , link : this . getDocsUrl ( "/doc/book/system-administration/" ) } ,
218+ { label : "- " + msg ( "Troubleshooting Jenkins" ) , link : this . getDocsUrl ( "/doc/book/troubleshooting/" ) } ,
219+ { label : "- " + msg ( "Terms and Definitions" ) , link : this . getDocsUrl ( "/doc/book/glossary/" ) } ,
220+
221+ { label : msg ( "Solution Pages" ) , link : this . getDocsUrl ( "/solutions" ) , header : true } ,
222+
188223 {
189- label : msg ( "Tutorials" ) , link : "/doc/tutorials" , header : true
224+ label : msg ( "Tutorials" ) ,
225+ link : this . getDocsUrl ( "/doc/tutorials" ) ,
226+ header : true
190227 } ,
228+
191229 {
192- label : msg ( "Developer Guide" ) , link : "/doc/developer" , header : true
230+ label : msg ( "Developer Guide" ) ,
231+ link : this . getDocsUrl ( "/doc/developer" ) ,
232+ header : true
193233 } ,
194- { label : msg ( "Contributor Guide" ) , link : "/participate" , header : true } ,
195- { label : msg ( "Books" ) , link : "/books" , header : true } ,
234+
235+ { label : msg ( "Contributor Guide" ) , link : this . getDocsUrl ( "/participate" ) , header : true } ,
236+ { label : msg ( "Books" ) , link : this . getDocsUrl ( "/books" ) , header : true } ,
196237 ]
197238 } ,
198239 { label : msg ( "Plugins" ) , link : "https://plugins.jenkins.io/" } ,
199240 {
200241 label : msg ( "Community" ) , link : [
201242 {
202- label : msg ( "Overview" ) , link : "/participate/"
243+ label : msg ( "Overview" ) , link : this . getDocsUrl ( "/participate/" )
203244 } ,
204245 {
205- label : msg ( "Chat" ) , link : "/chat/" , title : "Chat with the rest of the Jenkins community on IRC"
246+ label : msg ( "Chat" ) , link : this . getDocsUrl ( "/chat/" ) , title : "Chat with the rest of the Jenkins community on IRC"
206247 } ,
207- { label : msg ( "Meet" ) , link : "/projects/jam/" } ,
248+ { label : msg ( "Meet" ) , link : this . getDocsUrl ( "/projects/jam/" ) } ,
208249 {
209- label : msg ( "Events" ) , link : "/events/"
250+ label : msg ( "Events" ) , link : this . getDocsUrl ( "/events/" )
210251 } ,
211252 { label : msg ( "Forum" ) , link : "https://community.jenkins.io/" } ,
212253 { label : msg ( "Issue Tracker" ) , link : "https://issues.jenkins.io/" } ,
213- { label : msg ( "Mailing Lists" ) , link : "/mailing-lists/" , title : "Browse Jenkins mailing list archives and/ or subscribe to lists" } ,
214- { label : msg ( "Roadmap" ) , link : "/project/roadmap/" } ,
254+ { label : msg ( "Mailing Lists" ) , link : this . getDocsUrl ( "/mailing-lists/" ) , title : "Browse Jenkins mailing list archives and/ or subscribe to lists" } ,
255+ { label : msg ( "Roadmap" ) , link : this . getDocsUrl ( "/project/roadmap/" ) } ,
215256 { label : msg ( "Account Management" ) , link : "https://accounts.jenkins.io/" , title : "Create/manage your account for accessing wiki, issue tracker, etc" } ,
216257 {
217- label : msg ( "Special Interest Groups" ) , link : "/sigs/" , header : true
258+ label : msg ( "Special Interest Groups" ) , link : this . getDocsUrl ( "/sigs/" ) , header : true
218259 } ,
219- { label : "- " + msg ( "Advocacy and Outreach" ) , link : "/sigs/advocacy-and-outreach/" } ,
220- { label : "- " + msg ( "Documentation" ) , link : "/sigs/docs/" } ,
221- { label : "- " + msg ( "Google Summer of Code" ) , link : "/sigs/gsoc/" } ,
222- { label : "- " + msg ( "Platform" ) , link : "/sigs/platform/" } ,
223- { label : "- " + msg ( "User Experience" ) , link : "/sigs/ux/" } ,
260+ { label : "- " + msg ( "Advocacy and Outreach" ) , link : this . getDocsUrl ( "/sigs/advocacy-and-outreach/" ) } ,
261+ { label : "- " + msg ( "Documentation" ) , link : this . getDocsUrl ( "/sigs/docs/" ) } ,
262+ { label : "- " + msg ( "Google Summer of Code" ) , link : this . getDocsUrl ( "/sigs/gsoc/" ) } ,
263+ { label : "- " + msg ( "Platform" ) , link : this . getDocsUrl ( "/sigs/platform/" ) } ,
264+ { label : "- " + msg ( "User Experience" ) , link : this . getDocsUrl ( "/sigs/ux/" ) } ,
224265 ]
225266 } ,
226267 {
227268 label : msg ( "Subprojects" ) , link : [
228269 {
229- label : msg ( "Overview" ) , link : "/projects/"
270+ label : msg ( "Overview" ) , link : this . getDocsUrl ( "/projects/" )
230271 } ,
231- { label : msg ( "Google Summer of Code in Jenkins" ) , link : "/projects/gsoc/" } ,
232- { label : msg ( "Infrastructure" ) , link : "/projects/infrastructure/" } ,
233- { label : msg ( "CI/CD and Jenkins Area Meetups" ) , link : "/projects/jam/" } ,
234- { label : msg ( "Jenkins Configuration as Code" ) , link : "/projects/jcasc/" } ,
235- { label : msg ( "Jenkins Operator" ) , link : "/projects/jenkins-operator/" } ,
236- { label : msg ( "Jenkins Remoting" ) , link : "/projects/remoting/" } ,
237- { label : msg ( "Document Jenkins on Kubernetes" ) , link : "/sigs/docs/gsod/2020/projects/document-jenkins-on-kubernetes/" } ,
272+ { label : msg ( "Google Summer of Code in Jenkins" ) , link : this . getDocsUrl ( "/projects/gsoc/" ) } ,
273+ { label : msg ( "Infrastructure" ) , link : this . getDocsUrl ( "/projects/infrastructure/" ) } ,
274+ { label : msg ( "CI/CD and Jenkins Area Meetups" ) , link : this . getDocsUrl ( "/projects/jam/" ) } ,
275+ { label : msg ( "Jenkins Configuration as Code" ) , link : this . getDocsUrl ( "/projects/jcasc/" ) } ,
276+ { label : msg ( "Jenkins Operator" ) , link : this . getDocsUrl ( "/projects/jenkins-operator/" ) } ,
277+ { label : msg ( "Jenkins Remoting" ) , link : this . getDocsUrl ( "/projects/remoting/" ) } ,
278+ { label : msg ( "Document Jenkins on Kubernetes" ) , link : this . getDocsUrl ( "/sigs/docs/gsod/2020/projects/document-jenkins-on-kubernetes/" ) } ,
238279 ]
239280 } ,
240281 {
241282 label : msg ( "Security" ) , link : [
242283 {
243- label : msg ( "Overview" ) , link : "/security/"
284+ label : msg ( "Overview" ) , link : this . getDocsUrl ( "/security/" )
244285 } ,
245- { label : msg ( "Security Advisories" ) , link : "/security/advisories/" } ,
246- { label : msg ( "Reporting Vulnerabilities" ) , link : "/security/reporting/" } ,
286+ { label : msg ( "Security Advisories" ) , link : this . getDocsUrl ( "/security/advisories/" ) } ,
287+ { label : msg ( "Reporting Vulnerabilities" ) , link : this . getDocsUrl ( "/security/reporting/" ) } ,
247288 ]
248289 } ,
249290 {
250291 label : msg ( "About" ) , link : [
251- { label : msg ( "Roadmap" ) , link : "/project/roadmap/" } ,
292+ { label : msg ( "Roadmap" ) , link : this . getDocsUrl ( "/project/roadmap/" ) } ,
252293 {
253- label : msg ( "Press" ) , link : "/press/"
294+ label : msg ( "Press" ) , link : this . getDocsUrl ( "/press/" )
254295 } ,
255296 {
256- label : msg ( "Awards" ) , link : "/awards/"
297+ label : msg ( "Awards" ) , link : this . getDocsUrl ( "/awards/" )
257298 } ,
258- { label : msg ( "Conduct" ) , link : "/project/conduct/" } ,
259- { label : msg ( "Artwork" ) , link : "/artwork/" } ,
299+ { label : msg ( "Conduct" ) , link : this . getDocsUrl ( "/project/conduct/" ) } ,
300+ { label : msg ( "Artwork" ) , link : this . getDocsUrl ( "/artwork/" ) } ,
260301 ]
261302 }
262303 ] as Array < NavbarItemLink > ;
304+
263305 const menuItemsHtml = menuItems . map ( ( menuItem , idx ) => {
264306 let body ;
265307 if ( menuItem . link && Array . isArray ( menuItem . link ) ) {
266- // eslint-disable-next-line lit/no-this-assign-in-render
267308 body = this . renderNavItemDropdown ( menuItem , idx , this . visibleMenu === idx ) ;
268309 } else {
269- // eslint-disable-next-line lit/no-this-assign-in-render
270310 body = html `< li class ="nav-item "> ${ this . renderNavItemLink ( menuItem ) } </ li > ` ;
271311 }
272312 return body ;
273313 } ) ;
314+
315+ const versionSelector = this . docVersions . length > 1 ? html `
316+ < div class ="version-selector ">
317+ < select @change =${ this . _handleVersionChange } >
318+ ${ this . docVersions . map ( v => html `
319+ < option
320+ value =${ v . version }
321+ ?selected =${ v . version === this . currentDocVersion }
322+ >
323+ ${ v . label } (${ v . version } )
324+ </ option >
325+ ` ) }
326+ </ select >
327+ </ div >
328+ ` : nothing ;
329+
274330 const searchboxHtml = ! this . showSearchBox ? nothing : html `< jio-searchbox @click =${ this . _handleSearchboxClick } > </ jio-searchbox > ` ;
331+
275332 return html `
276333 < nav class ="navbar " data-theme =${ this . theme } >
277334 < span class ="navbar-brand ">
278335 < slot name ="brand ">
279336 < a href ="/ "> Jenkins</ a >
280337 </ slot >
281338 </ span >
339+
340+ ${ versionSelector }
341+
282342 < button
283343 class ="navbar-toggler collapsed btn "
284344 type ="button "
@@ -288,6 +348,7 @@ export class Navbar extends LitElement {
288348 aria-label="Toggle navigation">
289349 < ion-icon name =${ this . menuToggled ? "close-outline" : "menu-outline" } title ="Toggle Menu Visible"> </ ion-icon >
290350 </ button >
351+
291352 < div class ="navbar-menu collapse ${ this . menuToggled ? "show" : "" } ">
292353 < ul class ="nav navbar-nav mr-auto ">
293354 ${ this . renderNavItemDropdown ( { label : html `< jio-cdf-logo > </ jio-cdf-logo > ` , link : cdfMenuItems } , 99 , this . visibleMenu === 99 ) }
@@ -310,11 +371,6 @@ export class Navbar extends LitElement {
310371 const assignedElements = slotElement . assignedElements ( ) ;
311372 const container = slotElement . parentNode as HTMLElement ;
312373 for ( const element of assignedElements ) {
313- //if (element.slot === "rightMenuItems") {
314- // const divider = document.createElement('li');
315- // divider.className = "divider";
316- // container.appendChild(divider);
317- //}
318374 for ( const link of element . querySelectorAll ( 'jio-navbar-link' ) ) {
319375 const wrapper = document . createElement ( 'li' ) ;
320376 wrapper . className = "nav-item" ;
@@ -388,6 +444,4 @@ declare global {
388444 interface HTMLElementTagNameMap {
389445 'jio-navbar' : Navbar ;
390446 }
391- }
392-
393-
447+ }
0 commit comments