4141.suggestion-item : hover , .suggestion-item .selected {
4242 background-color : # f0f0f0 ;
4343}
44- # timeline {
44+ # timeline , . timeline {
4545 position : relative;
4646 border-left : 2px solid # ccc ;
4747 margin-left : 20px ;
8888 background : # f3f4f6 ;
8989 border-radius : 4px ;
9090}
91+ .api-section {
92+ margin-top : 2rem ;
93+ padding : 1rem ;
94+ border : 1px solid # e5e7eb ;
95+ border-radius : 8px ;
96+ }
97+ .api-section h2 {
98+ margin-top : 0 ;
99+ color : # 1f2937 ;
100+ font-size : 1.5rem ;
101+ }
102+ .api-info {
103+ margin : 1rem 0 ;
104+ }
105+ .api-info-item {
106+ margin : 0.5rem 0 ;
107+ }
108+ .api-info-label {
109+ font-weight : 600 ;
110+ color : # 4b5563 ;
111+ }
112+ .status-indicator {
113+ display : inline-block;
114+ padding : 0.25rem 0.5rem ;
115+ border-radius : 4px ;
116+ font-size : 0.875rem ;
117+ margin-right : 0.5rem ;
118+ }
119+ .status-experimental {
120+ background-color : # fef3c7 ;
121+ color : # 92400e ;
122+ }
123+ .status-deprecated {
124+ background-color : # fee2e2 ;
125+ color : # 991b1b ;
126+ }
127+ .status-standard {
128+ background-color : # d1fae5 ;
129+ color : # 065f46 ;
130+ }
91131@media (max-width : 600px ) {
92132 body {
93133 margin : 1rem ;
104144</ style >
105145</ head >
106146< body >
107- < body >
108147< h1 > MDN Browser Support Timelines</ h1 >
109148
110149< div id ="autocomplete-container ">
@@ -116,12 +155,14 @@ <h1>MDN Browser Support Timelines</h1>
116155 < div id ="timeline "> </ div >
117156</ div >
118157
158+ < div id ="api-details "> </ div >
159+
119160< script >
120161let allFiles = [ ] ;
121162let selectedFiles = [ ] ;
122163
123164async function fetchAllFiles ( repo , path = 'api' , page = 1 , allFiles = [ ] ) {
124- const token = '' ; // Optional: Add GitHub Personal Access Token for higher rate limits
165+ const token = '' ;
125166 const headers = token ? { 'Authorization' : `token ${ token } ` } : { } ;
126167
127168 try {
@@ -133,11 +174,8 @@ <h1>MDN Browser Support Timelines</h1>
133174 }
134175
135176 const files = await response . json ( ) ;
136-
137- // Add files to our collection
138177 allFiles . push ( ...files . map ( file => file . path ) ) ;
139178
140- // If we got 100 files, there might be more pages
141179 if ( files . length === 100 ) {
142180 return fetchAllFiles ( repo , path , page + 1 , allFiles ) ;
143181 }
@@ -149,6 +187,16 @@ <h1>MDN Browser Support Timelines</h1>
149187 }
150188}
151189
190+ function updateUrlHash ( filePath ) {
191+ const cleanPath = filePath . replace ( / ^ a p i \/ / , '' ) ;
192+ history . pushState ( null , '' , `#${ cleanPath } ` ) ;
193+ }
194+
195+ function getHashPath ( ) {
196+ const hash = window . location . hash . slice ( 1 ) ;
197+ return hash ? `api/${ hash } ` : null ;
198+ }
199+
152200function setupAutocomplete ( ) {
153201 const searchInput = document . getElementById ( 'search-input' ) ;
154202 const suggestionsContainer = document . getElementById ( 'suggestions' ) ;
@@ -157,12 +205,10 @@ <h1>MDN Browser Support Timelines</h1>
157205 const searchTerm = searchInput . value . toLowerCase ( ) ;
158206 const filteredFiles = allFiles . filter ( file =>
159207 file . toLowerCase ( ) . includes ( searchTerm )
160- ) . slice ( 0 , 20 ) ; // Limit to 20 suggestions
208+ ) . slice ( 0 , 20 ) ;
161209
162- // Clear previous suggestions
163210 suggestionsContainer . innerHTML = '' ;
164211
165- // Create suggestion items
166212 filteredFiles . forEach ( ( file , index ) => {
167213 const suggestionItem = document . createElement ( 'div' ) ;
168214 suggestionItem . textContent = file ;
@@ -179,47 +225,118 @@ <h1>MDN Browser Support Timelines</h1>
179225 } ) ;
180226 } ) ;
181227
182- // Keyboard navigation
183- searchInput . addEventListener ( 'keydown' , ( e ) => {
184- const suggestions = suggestionsContainer . children ;
228+ searchInput . addEventListener ( 'keydown' , handleKeyboardNavigation ) ;
229+ }
230+
231+ function handleKeyboardNavigation ( e ) {
232+ const suggestionsContainer = document . getElementById ( 'suggestions' ) ;
233+ const suggestions = suggestionsContainer . children ;
234+
235+ if ( e . key === 'ArrowDown' || e . key === 'ArrowUp' ) {
236+ e . preventDefault ( ) ;
237+ if ( suggestions . length > 0 ) {
238+ const currentSelected = suggestionsContainer . querySelector ( '.selected' ) ;
239+ const nextIndex = currentSelected
240+ ? Math . min ( parseInt ( currentSelected . getAttribute ( 'data-index' ) ) + ( e . key === 'ArrowDown' ? 1 : - 1 ) , suggestions . length - 1 )
241+ : 0 ;
242+
243+ if ( currentSelected ) currentSelected . classList . remove ( 'selected' ) ;
244+ suggestions [ nextIndex ] . classList . add ( 'selected' ) ;
245+ }
246+ } else if ( e . key === 'Enter' ) {
247+ const selectedItem = suggestionsContainer . querySelector ( '.selected' ) ||
248+ suggestionsContainer . children [ 0 ] ;
185249
186- if ( e . key === 'ArrowDown' ) {
187- e . preventDefault ( ) ;
188- if ( suggestions . length > 0 ) {
189- const currentSelected = suggestionsContainer . querySelector ( '.selected' ) ;
190- const nextIndex = currentSelected
191- ? Math . min ( parseInt ( currentSelected . getAttribute ( 'data-index' ) ) + 1 , suggestions . length - 1 )
192- : 0 ;
193-
194- if ( currentSelected ) currentSelected . classList . remove ( 'selected' ) ;
195- suggestions [ nextIndex ] . classList . add ( 'selected' ) ;
196- }
197- } else if ( e . key === 'ArrowUp' ) {
250+ if ( selectedItem ) {
198251 e . preventDefault ( ) ;
199- if ( suggestions . length > 0 ) {
200- const currentSelected = suggestionsContainer . querySelector ( '.selected' ) ;
201- const prevIndex = currentSelected
202- ? Math . max ( parseInt ( currentSelected . getAttribute ( 'data-index' ) ) - 1 , 0 )
203- : suggestions . length - 1 ;
204-
205- if ( currentSelected ) currentSelected . classList . remove ( 'selected' ) ;
206- suggestions [ prevIndex ] . classList . add ( 'selected' ) ;
207- }
208- } else if ( e . key === 'Enter' ) {
209- const selectedItem = suggestionsContainer . querySelector ( '.selected' ) ||
210- suggestionsContainer . children [ 0 ] ;
211-
212- if ( selectedItem ) {
213- searchInput . value = selectedItem . textContent ;
214- suggestionsContainer . innerHTML = '' ;
215- fetchBrowserCompatData ( selectedItem . textContent ) ;
216- }
252+ const searchInput = document . getElementById ( 'search-input' ) ;
253+ searchInput . value = selectedItem . textContent ;
254+ suggestionsContainer . innerHTML = '' ;
255+ fetchBrowserCompatData ( selectedItem . textContent ) ;
256+ }
257+ }
258+ }
259+
260+ function renderApiSection ( data ) {
261+ const apiDetails = document . getElementById ( 'api-details' ) ;
262+ apiDetails . innerHTML = '' ;
263+
264+ // Render main API section
265+ const mainSection = createApiSection ( data . data . __compat , data . query ) ;
266+ apiDetails . appendChild ( mainSection ) ;
267+
268+ // Render sub-features
269+ Object . entries ( data . data ) . forEach ( ( [ key , value ] ) => {
270+ if ( key !== '__compat' && value . __compat ) {
271+ const subSection = createApiSection ( value . __compat , `${ data . query } .${ key } ` ) ;
272+ apiDetails . appendChild ( subSection ) ;
217273 }
218274 } ) ;
219275}
220276
277+ function createApiSection ( compat , title ) {
278+ const section = document . createElement ( 'section' ) ;
279+ section . className = 'api-section' ;
280+
281+ const heading = document . createElement ( 'h2' ) ;
282+ heading . textContent = title ;
283+ section . appendChild ( heading ) ;
284+
285+ const info = document . createElement ( 'div' ) ;
286+ info . className = 'api-info' ;
287+
288+ // Add MDN link if available
289+ if ( compat . mdn_url ) {
290+ const mdnLink = document . createElement ( 'div' ) ;
291+ mdnLink . className = 'api-info-item' ;
292+ const mdnUrl = compat . mdn_url . replace ( '/en-US/docs/Web/API/' , 'https://developer.mozilla.org/en-US/docs/Web/API/' ) ;
293+ mdnLink . innerHTML = `<span class="api-info-label">MDN Documentation:</span> <a href="${ mdnUrl } " target="_blank">${ mdnUrl } </a>` ;
294+ info . appendChild ( mdnLink ) ;
295+ }
296+
297+ // Add specification link if available
298+ if ( compat . spec_url ) {
299+ const specLink = document . createElement ( 'div' ) ;
300+ specLink . className = 'api-info-item' ;
301+ specLink . innerHTML = `<span class="api-info-label">Specification:</span> <a href="${ compat . spec_url } " target="_blank">${ compat . spec_url } </a>` ;
302+ info . appendChild ( specLink ) ;
303+ }
304+
305+ // Add status indicators
306+ if ( compat . status ) {
307+ const statusDiv = document . createElement ( 'div' ) ;
308+ statusDiv . className = 'api-info-item' ;
309+
310+ Object . entries ( compat . status ) . forEach ( ( [ key , value ] ) => {
311+ if ( value ) {
312+ const status = document . createElement ( 'span' ) ;
313+ status . className = `status-indicator status-${ key } ` ;
314+ status . textContent = key . charAt ( 0 ) . toUpperCase ( ) + key . slice ( 1 ) ;
315+ statusDiv . appendChild ( status ) ;
316+ }
317+ } ) ;
318+
319+ info . appendChild ( statusDiv ) ;
320+ }
321+
322+ section . appendChild ( info ) ;
323+
324+ // Add timeline for this section
325+ const timelineDiv = document . createElement ( 'div' ) ;
326+ timelineDiv . className = 'timeline' ;
327+ console . log ( { compat} ) ;
328+ const supportData = extractSupportData ( {
329+ browsers : compat . support
330+ } ) ;
331+ renderTimeline ( supportData , timelineDiv ) ;
332+ section . appendChild ( timelineDiv ) ;
333+
334+ return section ;
335+ }
336+
221337async function fetchBrowserCompatData ( filePath ) {
222338 try {
339+ updateUrlHash ( filePath ) ;
223340 const url = `https://bcd.developer.mozilla.org/bcd/api/v0/current/${ filePath . replace ( '/' , '.' ) } ` ;
224341 const response = await fetch ( url ) ;
225342
@@ -228,7 +345,9 @@ <h1>MDN Browser Support Timelines</h1>
228345 }
229346
230347 const data = await response . json ( ) ;
231- renderTimeline ( extractSupportData ( data ) ) ;
348+ const timelineData = extractSupportData ( data ) ;
349+ renderTimeline ( timelineData ) ;
350+ renderApiSection ( data ) ;
232351 } catch ( error ) {
233352 console . error ( 'Error fetching browser compat data:' , error ) ;
234353 renderErrorMessage ( error . message ) ;
@@ -237,12 +356,11 @@ <h1>MDN Browser Support Timelines</h1>
237356
238357function extractSupportData ( data ) {
239358 const browsers = data . browsers ;
240- const support = data . data . __compat . support ;
241359
242360 const supportData = [ ] ;
243361 const notSupported = [ ] ;
244362
245- for ( const [ browserName , supportInfo ] of Object . entries ( support ) ) {
363+ for ( const [ browserName , supportInfo ] of Object . entries ( browsers ) ) {
246364 if ( ! supportInfo [ 0 ] ) continue ;
247365
248366 if ( supportInfo [ 0 ] . version_added === false ) {
@@ -273,11 +391,9 @@ <h1>MDN Browser Support Timelines</h1>
273391 } ) ;
274392}
275393
276- function renderTimeline ( data ) {
277- const timeline = document . getElementById ( 'timeline' ) ;
278- timeline . innerHTML = '' ;
394+ function renderTimeline ( data , container = document . getElementById ( 'timeline' ) ) {
395+ container . innerHTML = '' ;
279396
280- // Add supported browsers timeline
281397 data . supported . forEach ( item => {
282398 const event = document . createElement ( 'div' ) ;
283399 event . className = 'event' ;
@@ -288,24 +404,23 @@ <h1>MDN Browser Support Timelines</h1>
288404 <span class="event-version">v${ item . version } </span>
289405 </div>
290406 ` ;
291- timeline . appendChild ( event ) ;
407+ container . appendChild ( event ) ;
292408 } ) ;
293409
294- // Add not supported browsers section if any
295410 if ( data . notSupported . length > 0 ) {
296411 const notSupportedDiv = document . createElement ( 'div' ) ;
297412 notSupportedDiv . className = 'not-supported' ;
298413 notSupportedDiv . innerHTML = `
299414 <strong>Not Supported:</strong> ${ data . notSupported . join ( ', ' ) }
300415 ` ;
301- timeline . appendChild ( notSupportedDiv ) ;
416+ container . appendChild ( notSupportedDiv ) ;
302417 }
303418}
304419
305420function renderErrorMessage ( message ) {
306421 const timeline = document . getElementById ( 'timeline' ) ;
307422 timeline . innerHTML = `
308- <div class="error">
423+ <div class="error" style="display: block;" >
309424 Error: ${ message }
310425 </div>
311426 ` ;
@@ -315,10 +430,25 @@ <h1>MDN Browser Support Timelines</h1>
315430async function init ( ) {
316431 allFiles = await fetchAllFiles ( 'mdn/browser-compat-data' , 'api' ) ;
317432 setupAutocomplete ( ) ;
433+
434+ // Check for hash in URL and load that file if present
435+ const hashPath = getHashPath ( ) ;
436+ if ( hashPath ) {
437+ document . getElementById ( 'search-input' ) . value = hashPath ;
438+ fetchBrowserCompatData ( hashPath ) ;
439+ }
318440}
319441
442+ // Handle back/forward navigation
443+ window . addEventListener ( 'popstate' , ( ) => {
444+ const hashPath = getHashPath ( ) ;
445+ if ( hashPath ) {
446+ document . getElementById ( 'search-input' ) . value = hashPath ;
447+ fetchBrowserCompatData ( hashPath ) ;
448+ }
449+ } ) ;
450+
320451init ( ) ;
321452</ script >
322453</ body >
323- </ body >
324454</ html >
0 commit comments