Skip to content

Instantly share code, notes, and snippets.

@simonw

simonw/enhanc.md Secret

Created November 11, 2024 02:21
Show Gist options
  • Save simonw/8c71a931921789e11f1d33f09d9ad9ae to your computer and use it in GitHub Desktop.
Save simonw/8c71a931921789e11f1d33f09d9ad9ae to your computer and use it in GitHub Desktop.

Enhancing API Feature Display with URL Hash

human (Nov 10, 2024, 06:14 PM)

The JSON this fetches looks like this - note how the data __compat bit at the start has a whole bunch of additional stuff in it Don't use react - modify the HTML I gave you. Keep the current timeline but underneath it add a chunk for each of the things in data with as much useful information as possible, including nested timelines - and turn the MDN links that start /en-US/docs/Web/API/AnimationEffect into links to https://developer.mozilla.org/en-US/docs/Web/API/AnimationEffect add one more feature: when a page is selected add its path to the # fragment hash in the URL using the history API. when the page first loads look for that fragment and navigate there and populate the search box if it is present

paste.txt

<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
* {
  box-sizing: border-box;
}
body {
  font-family: Helvetica, Arial, sans-serif;
  font-size: 16px;
  margin: 2rem 1rem;
  max-width: 800px;
  margin: 0 auto;
}
#autocomplete-container {
  position: relative;
  width: 100%;
}
#search-input {
  width: 100%;
  padding: 10px;
  font-size: 16px;
}
#suggestions {
  position: absolute;
  top: 100%;
  left: 0;
  right: 0;
  max-height: 300px;
  overflow-y: auto;
  border: 1px solid #ddd;
  background: white;
  z-index: 10;
  margin: 0 15px;
}
.suggestion-item {
  padding: 10px;
  cursor: pointer;
}
.suggestion-item:hover, .suggestion-item.selected {
  background-color: #f0f0f0;
}
#timeline {
  position: relative;
  border-left: 2px solid #ccc;
  margin-left: 20px;
  padding-left: 20px;
  margin-top: 2em;
}
.event {
  margin-bottom: 20px;
  position: relative;
}
.event::before {
  content: '';
  position: absolute;
  left: -26px;
  top: 5px;
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: #3b82f6;
}
.event-date {
  font-size: 0.875rem;
  color: #666;
}
.event-browser {
  font-weight: 600;
  color: #1f2937;
}
.event-version {
  color: #059669;
  margin-left: 4px;
}
.error {
  color: #dc2626;
  margin-top: 8px;
  display: none;
  padding: 8px;
  background: #fef2f2;
  border-radius: 4px;
}
.not-supported {
  margin-top: 20px;
  padding: 12px;
  background: #f3f4f6;
  border-radius: 4px;
}
@media (max-width: 600px) {
  body {
    margin: 1rem;
    font-size: 14px;
  }
  #timeline {
    margin-left: 10px;
    padding-left: 10px;
  }
  .event::before {
    left: -16px;
  }
}
</style>
</head>
<body>
<body>
<h1>MDN Browser Support Timelines</h1>

<div id="autocomplete-container">
  <input type="text" id="search-input" placeholder="Search files...">
  <div id="suggestions"></div>
</div>

<div id="timeline-section">
  <div id="timeline"></div>
</div>

<script>
let allFiles = [];
let selectedFiles = [];

async function fetchAllFiles(repo, path = 'api', page = 1, allFiles = []) {
  const token = ''; // Optional: Add GitHub Personal Access Token for higher rate limits
  const headers = token ? { 'Authorization': `token ${token}` } : {};
  
  try {
    const url = `https://api.github.com/repos/${repo}/contents/${path}?page=${page}&per_page=100`;
    const response = await fetch(url, { headers });
    
    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }
    
    const files = await response.json();
    
    // Add files to our collection
    allFiles.push(...files.map(file => file.path));
    
    // If we got 100 files, there might be more pages
    if (files.length === 100) {
      return fetchAllFiles(repo, path, page + 1, allFiles);
    }
    
    return allFiles;
  } catch (error) {
    console.error('Error fetching files:', error);
    return allFiles;
  }
}

function setupAutocomplete() {
  const searchInput = document.getElementById('search-input');
  const suggestionsContainer = document.getElementById('suggestions');

  searchInput.addEventListener('input', () => {
    const searchTerm = searchInput.value.toLowerCase();
    const filteredFiles = allFiles.filter(file => 
      file.toLowerCase().includes(searchTerm)
    ).slice(0, 20); // Limit to 20 suggestions

    // Clear previous suggestions
    suggestionsContainer.innerHTML = '';

    // Create suggestion items
    filteredFiles.forEach((file, index) => {
      const suggestionItem = document.createElement('div');
      suggestionItem.textContent = file;
      suggestionItem.className = 'suggestion-item';
      suggestionItem.setAttribute('data-index', index);

      suggestionItem.addEventListener('click', () => {
        searchInput.value = file;
        suggestionsContainer.innerHTML = '';
        fetchBrowserCompatData(file);
      });

      suggestionsContainer.appendChild(suggestionItem);
    });
  });

  // Keyboard navigation
  searchInput.addEventListener('keydown', (e) => {
    const suggestions = suggestionsContainer.children;
    
    if (e.key === 'ArrowDown') {
      e.preventDefault();
      if (suggestions.length > 0) {
        const currentSelected = suggestionsContainer.querySelector('.selected');
        const nextIndex = currentSelected 
          ? Math.min(parseInt(currentSelected.getAttribute('data-index')) + 1, suggestions.length - 1)
          : 0;
        
        if (currentSelected) currentSelected.classList.remove('selected');
        suggestions[nextIndex].classList.add('selected');
      }
    } else if (e.key === 'ArrowUp') {
      e.preventDefault();
      if (suggestions.length > 0) {
        const currentSelected = suggestionsContainer.querySelector('.selected');
        const prevIndex = currentSelected
          ? Math.max(parseInt(currentSelected.getAttribute('data-index')) - 1, 0)
          : suggestions.length - 1;
        
        if (currentSelected) currentSelected.classList.remove('selected');
        suggestions[prevIndex].classList.add('selected');
      }
    } else if (e.key === 'Enter') {
      const selectedItem = suggestionsContainer.querySelector('.selected') || 
                           suggestionsContainer.children[0];
      
      if (selectedItem) {
        searchInput.value = selectedItem.textContent;
        suggestionsContainer.innerHTML = '';
        fetchBrowserCompatData(selectedItem.textContent);
      }
    }
  });
}

async function fetchBrowserCompatData(filePath) {
  try {
    const url = `https://bcd.developer.mozilla.org/bcd/api/v0/current/${filePath.replace('/', '.')}`;
    const response = await fetch(url);
    
    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }
    
    const data = await response.json();
    renderTimeline(extractSupportData(data));
  } catch (error) {
    console.error('Error fetching browser compat data:', error);
    renderErrorMessage(error.message);
  }
}

function extractSupportData(data) {
  const browsers = data.browsers;
  const support = data.data.__compat.support;
        
  const supportData = [];
  const notSupported = [];
        
  for (const [browserName, supportInfo] of Object.entries(support)) {
    if (!supportInfo[0]) continue;
        
    if (supportInfo[0].version_added === false) {
      notSupported.push(browsers[browserName]?.name || browserName);
      continue;
    }
        
    if (!supportInfo[0].version_added || !supportInfo[0].release_date) continue;
        
    supportData.push({
      browser: browsers[browserName]?.name || browserName,
      version: supportInfo[0].version_added,
      date: supportInfo[0].release_date
    });
  }
        
  return {
    supported: supportData.sort((a, b) => new Date(a.date) - new Date(b.date)),
    notSupported
  };
}

function formatDate(dateStr) {
  return new Date(dateStr).toLocaleDateString('en-US', {
    year: 'numeric',
    month: 'long',
    day: 'numeric'
  });
}

function renderTimeline(data) {
  const timeline = document.getElementById('timeline');
  timeline.innerHTML = '';
        
  // Add supported browsers timeline
  data.supported.forEach(item => {
    const event = document.createElement('div');
    event.className = 'event';
    event.innerHTML = `
      <div class="event-date">${formatDate(item.date)}</div>
      <div>
        <span class="event-browser">${item.browser}</span>
        <span class="event-version">v${item.version}</span>
      </div>
    `;
    timeline.appendChild(event);
  });
   
  // Add not supported browsers section if any
  if (data.notSupported.length > 0) {
    const notSupportedDiv = document.createElement('div');
    notSupportedDiv.className = 'not-supported';
    notSupportedDiv.innerHTML = `
      <strong>Not Supported:</strong> ${data.notSupported.join(', ')}
    `;
    timeline.appendChild(notSupportedDiv);
  }
}

function renderErrorMessage(message) {
  const timeline = document.getElementById('timeline');
  timeline.innerHTML = `
    <div class="error">
      Error: ${message}
    </div>
  `;
}

// Initialize the app
async function init() {
  allFiles = await fetchAllFiles('mdn/browser-compat-data', 'api');
  setupAutocomplete();
}

init();
</script>
</body>
</body>
</html>


paste-2.txt

{
    "data": {
        "__compat": {
            "mdn_url": "/en-US/docs/Web/API/AnimationEffect",
            "source_file": "api/AnimationEffect.json",
            "spec_url": "https://drafts.csswg.org/web-animations-1/#the-animationeffect-interface",
            "status": {
                "deprecated": false,
                "experimental": false,
                "standard_track": true
            },
            "support": {
                "chrome": [
                    {
                        "version_added": "75",
                        "release_date": "2019-06-04"
                    }
                ],
                "chrome_android": [
                    {
                        "version_added": "75",
                        "release_date": "2019-06-04"
                    }
                ],
                "edge": [
                    {
                        "version_added": "79",
                        "release_date": "2020-01-15"
                    }
                ],
                "firefox": [
                    {
                        "version_added": "63",
                        "release_date": "2018-10-23"
                    },
                    {
                        "alternative_name": "AnimationEffectReadOnly",
                        "version_added": "48",
                        "version_last": "62",
                        "version_removed": "63",
                        "release_date": "2016-08-02"
                    }
                ],
                "firefox_android": [
                    {
                        "version_added": "63",
                        "release_date": "2018-10-23"
                    },
                    {
                        "alternative_name": "AnimationEffectReadOnly",
                        "version_added": "48",
                        "version_last": "62",
                        "version_removed": "63",
                        "release_date": "2016-08-02"
                    }
                ],
                "ie": [
                    {
                        "version_added": false
                    }
                ],
                "oculus": [
                    {
                        "version_added": "7.0"
                    }
                ],
                "opera": [
                    {
                        "version_added": "62",
                        "release_date": "2019-06-27"
                    }
                ],
                "opera_android": [
                    {
                        "version_added": "54",
                        "release_date": "2019-10-18"
                    }
                ],
                "safari": [
                    {
                        "version_added": "13.1",
                        "release_date": "2020-03-24"
                    }
                ],
                "safari_ios": [
                    {
                        "version_added": "13.4",
                        "release_date": "2020-03-24"
                    }
                ],
                "samsunginternet_android": [
                    {
                        "version_added": "11.0",
                        "release_date": "2019-12-05"
                    }
                ],
                "webview_android": [
                    {
                        "version_added": "75",
                        "release_date": "2019-06-04"
                    }
                ],
                "webview_ios": [
                    {
                        "version_added": "13.4",
                        "release_date": "2020-03-24"
                    }
                ]
            },
            "tags": [
                "web-features:web-animations"
            ]
        },
        "getComputedTiming": {
            "__compat": {
                "mdn_url": "/en-US/docs/Web/API/AnimationEffect/getComputedTiming",
                "source_file": "api/AnimationEffect.json",
                "spec_url": "https://drafts.csswg.org/web-animations-1/#dom-animationeffect-getcomputedtiming",
                "status": {
                    "deprecated": false,
                    "experimental": false,
                    "standard_track": true
                },
                "support": {
                    "chrome": [
                        {
                            "version_added": "75",
                            "release_date": "2019-06-04"
                        }
                    ],
                    "chrome_android": [
                        {
                            "version_added": "75",
                            "release_date": "2019-06-04"
                        }
                    ],
                    "edge": [
                        {
                            "version_added": "79",
                            "release_date": "2020-01-15"
                        }
                    ],
                    "firefox": [
                        {
                            "version_added": "63",
                            "release_date": "2018-10-23"
                        }
                    ],
                    "firefox_android": [
                        {
                            "version_added": "63",
                            "release_date": "2018-10-23"
                        }
                    ],
                    "ie": [
                        {
                            "version_added": false
                        }
                    ],
                    "oculus": [
                        {
                            "version_added": "7.0"
                        }
                    ],
                    "opera": [
                        {
                            "version_added": "62",
                            "release_date": "2019-06-27"
                        }
                    ],
                    "opera_android": [
                        {
                            "version_added": "54",
                            "release_date": "2019-10-18"
                        }
                    ],
                    "safari": [
                        {
                            "version_added": "13.1",
                            "release_date": "2020-03-24"
                        }
                    ],
                    "safari_ios": [
                        {
                            "version_added": "13.4",
                            "release_date": "2020-03-24"
                        }
                    ],
                    "samsunginternet_android": [
                        {
                            "version_added": "11.0",
                            "release_date": "2019-12-05"
                        }
                    ],
                    "webview_android": [
                        {
                            "version_added": "75",
                            "release_date": "2019-06-04"
                        }
                    ],
                    "webview_ios": [
                        {
                            "version_added": "13.4",
                            "release_date": "2020-03-24"
                        }
                    ]
                },
                "tags": [
                    "web-features:web-animations"
                ]
            }
        },
        "getTiming": {
            "__compat": {
                "mdn_url": "/en-US/docs/Web/API/AnimationEffect/getTiming",
                "source_file": "api/AnimationEffect.json",
                "spec_url": "https://drafts.csswg.org/web-animations-1/#dom-animationeffect-gettiming",
                "status": {
                    "deprecated": false,
                    "experimental": false,
                    "standard_track": true
                },
                "support": {
                    "chrome": [
                        {
                            "version_added": "75",
                            "release_date": "2019-06-04"
                        }
                    ],
                    "chrome_android": [
                        {
                            "version_added": "75",
                            "release_date": "2019-06-04"
                        }
                    ],
                    "edge": [
                        {
                            "version_added": "79",
                            "release_date": "2020-01-15"
                        }
                    ],
                    "firefox": [
                        {
                            "version_added": "63",
                            "release_date": "2018-10-23"
                        }
                    ],
                    "firefox_android": [
                        {
                            "version_added": "63",
                            "release_date": "2018-10-23"
                        }
                    ],
                    "ie": [
                        {
                            "version_added": false
                        }
                    ],
                    "oculus": [
                        {
                            "version_added": "7.0"
                        }
                    ],
                    "opera": [
                        {
                            "version_added": "62",
                            "release_date": "2019-06-27"
                        }
                    ],
                    "opera_android": [
                        {
                            "version_added": "54",
                            "release_date": "2019-10-18"
                        }
                    ],
                    "safari": [
                        {
                            "version_added": "13.1",
                            "release_date": "2020-03-24"
                        }
                    ],
                    "safari_ios": [
                        {
                            "version_added": "13.4",
                            "release_date": "2020-03-24"
                        }
                    ],
                    "samsunginternet_android": [
                        {
                            "version_added": "11.0",
                            "release_date": "2019-12-05"
                        }
                    ],
                    "webview_android": [
                        {
                            "version_added": "75",
                            "release_date": "2019-06-04"
                        }
                    ],
                    "webview_ios": [
                        {
                            "version_added": "13.4",
                            "release_date": "2020-03-24"
                        }
                    ]
                },
                "tags": [
                    "web-features:web-animations"
                ]
            }
        },
        "updateTiming": {
            "__compat": {
                "mdn_url": "/en-US/docs/Web/API/AnimationEffect/updateTiming",
                "source_file": "api/AnimationEffect.json",
                "spec_url": "https://drafts.csswg.org/web-animations-1/#dom-animationeffect-updatetiming",
                "status": {
                    "deprecated": false,
                    "experimental": false,
                    "standard_track": true
                },
                "support": {
                    "chrome": [
                        {
                            "version_added": "75",
                            "release_date": "2019-06-04"
                        }
                    ],
                    "chrome_android": [
                        {
                            "version_added": "75",
                            "release_date": "2019-06-04"
                        }
                    ],
                    "edge": [
                        {
                            "version_added": "79",
                            "release_date": "2020-01-15"
                        }
                    ],
                    "firefox": [
                        {
                            "version_added": "63",
                            "release_date": "2018-10-23"
                        }
                    ],
                    "firefox_android": [
                        {
                            "version_added": "63",
                            "release_date": "2018-10-23"
                        }
                    ],
                    "ie": [
                        {
                            "version_added": false
                        }
                    ],
                    "oculus": [
                        {
                            "version_added": "7.0"
                        }
                    ],
                    "opera": [
                        {
                            "version_added": "62",
                            "release_date": "2019-06-27"
                        }
                    ],
                    "opera_android": [
                        {
                            "version_added": "54",
                            "release_date": "2019-10-18"
                        }
                    ],
                    "safari": [
                        {
                            "version_added": "13.1",
                            "release_date": "2020-03-24"
                        }
                    ],
                    "safari_ios": [
                        {
                            "version_added": "13.4",
                            "release_date": "2020-03-24"
                        }
                    ],
                    "samsunginternet_android": [
                        {
                            "version_added": "11.0",
                            "release_date": "2019-12-05"
                        }
                    ],
                    "webview_android": [
                        {
                            "version_added": "75",
                            "release_date": "2019-06-04"
                        }
                    ],
                    "webview_ios": [
                        {
                            "version_added": "13.4",
                            "release_date": "2020-03-24"
                        }
                    ]
                },
                "tags": [
                    "web-features:web-animations"
                ]
            }
        }
    },
    "query": "api.AnimationEffect",
    "browsers": {
        "chrome": {
            "accepts_flags": true,
            "accepts_webextensions": true,
            "name": "Chrome",
            "pref_url": "chrome://flags",
            "preview_name": "Canary",
            "releases": {
                "131": {
                    "engine": "Blink",
                    "engine_version": "131",
                    "release_date": "2024-11-12",
                    "status": "current"
                },
                "132": {
                    "engine": "Blink",
                    "engine_version": "132",
                    "release_date": "2025-01-14",
                    "status": "beta"
                },
                "133": {
                    "engine": "Blink",
                    "engine_version": "133",
                    "release_date": "2025-02-04",
                    "status": "nightly"
                },
                "134": {
                    "engine": "Blink",
                    "engine_version": "134",
                    "status": "planned"
                }
            },
            "type": "desktop"
        },
        "chrome_android": {
            "accepts_flags": true,
            "accepts_webextensions": false,
            "name": "Chrome Android",
            "pref_url": "chrome://flags",
            "releases": {
                "131": {
                    "engine": "Blink",
                    "engine_version": "131",
                    "release_date": "2024-11-12",
                    "status": "current"
                },
                "132": {
                    "engine": "Blink",
                    "engine_version": "132",
                    "release_date": "2025-01-14",
                    "status": "beta"
                },
                "133": {
                    "engine": "Blink",
                    "engine_version": "133",
                    "release_date": "2025-02-04",
                    "status": "nightly"
                },
                "134": {
                    "engine": "Blink",
                    "engine_version": "134",
                    "status": "planned"
                }
            },
            "type": "mobile",
            "upstream": "chrome"
        },
        "deno": {
            "accepts_flags": true,
            "accepts_webextensions": false,
            "name": "Deno",
            "releases": {
                "1.45": {
                    "engine": "V8",
                    "engine_version": "12.7",
                    "release_date": "2024-07-11",
                    "release_notes": "https://deno.com/blog/v1.45",
                    "status": "current"
                }
            },
            "type": "server"
        },
        "edge": {
            "accepts_flags": true,
            "accepts_webextensions": true,
            "name": "Edge",
            "pref_url": "about:flags",
            "releases": {
                "130": {
                    "engine": "Blink",
                    "engine_version": "130",
                    "release_date": "2024-10-17",
                    "release_notes": "https://learn.microsoft.com/en-us/deployedge/microsoft-edge-relnote-stable-channel#version-1300284946-october-17-2024",
                    "status": "current"
                },
                "131": {
                    "engine": "Blink",
                    "engine_version": "131",
                    "release_date": "2024-11-14",
                    "status": "beta"
                },
                "132": {
                    "engine": "Blink",
                    "engine_version": "132",
                    "release_date": "2025-01-09",
                    "status": "nightly"
                },
                "133": {
                    "engine": "Blink",
                    "engine_version": "133",
                    "release_date": "2025-02-06",
                    "status": "planned"
                }
            },
            "type": "desktop",
            "upstream": "chrome"
        },
        "firefox": {
            "accepts_flags": true,
            "accepts_webextensions": true,
            "name": "Firefox",
            "pref_url": "about:config",
            "preview_name": "Nightly",
            "releases": {
                "128": {
                    "engine": "Gecko",
                    "engine_version": "128",
                    "release_date": "2024-07-09",
                    "release_notes": "https://developer.mozilla.org/docs/Mozilla/Firefox/Releases/128",
                    "status": "esr"
                },
                "132": {
                    "engine": "Gecko",
                    "engine_version": "132",
                    "release_date": "2024-10-29",
                    "release_notes": "https://developer.mozilla.org/docs/Mozilla/Firefox/Releases/132",
                    "status": "current"
                },
                "133": {
                    "engine": "Gecko",
                    "engine_version": "133",
                    "release_date": "2024-11-26",
                    "release_notes": "https://developer.mozilla.org/docs/Mozilla/Firefox/Releases/133",
                    "status": "beta"
                },
                "134": {
                    "engine": "Gecko",
                    "engine_version": "134",
                    "release_date": "2025-01-07",
                    "release_notes": "https://developer.mozilla.org/docs/Mozilla/Firefox/Releases/134",
                    "status": "nightly"
                },
                "135": {
                    "engine": "Gecko",
                    "engine_version": "135",
                    "release_date": "2025-02-04",
                    "release_notes": "https://developer.mozilla.org/docs/Mozilla/Firefox/Releases/135",
                    "status": "planned"
                }
            },
            "type": "desktop"
        },
        "firefox_android": {
            "accepts_flags": false,
            "accepts_webextensions": true,
            "name": "Firefox for Android",
            "pref_url": "about:config",
            "releases": {
                "128": {
                    "engine": "Gecko",
                    "engine_version": "128",
                    "release_date": "2024-07-09",
                    "release_notes": "https://developer.mozilla.org/docs/Mozilla/Firefox/Releases/128",
                    "status": "esr"
                },
                "132": {
                    "engine": "Gecko",
                    "engine_version": "132",
                    "release_date": "2024-10-29",
                    "release_notes": "https://developer.mozilla.org/docs/Mozilla/Firefox/Releases/132",
                    "status": "current"
                },
                "133": {
                    "engine": "Gecko",
                    "engine_version": "133",
                    "release_date": "2024-11-26",
                    "release_notes": "https://developer.mozilla.org/docs/Mozilla/Firefox/Releases/133",
                    "status": "beta"
                },
                "134": {
                    "engine": "Gecko",
                    "engine_version": "134",
                    "release_date": "2025-01-07",
                    "release_notes": "https://developer.mozilla.org/docs/Mozilla/Firefox/Releases/134",
                    "status": "nightly"
                },
                "135": {
                    "engine": "Gecko",
                    "engine_version": "135",
                    "release_date": "2025-02-04",
                    "release_notes": "https://developer.mozilla.org/docs/Mozilla/Firefox/Releases/135",
                    "status": "planned"
                }
            },
            "type": "mobile",
            "upstream": "firefox"
        },
        "ie": {
            "accepts_flags": false,
            "accepts_webextensions": false,
            "name": "Internet Explorer",
            "releases": {},
            "type": "desktop"
        },
        "nodejs": {
            "accepts_flags": true,
            "accepts_webextensions": false,
            "name": "Node.js",
            "releases": {
                "22.0.0": {
                    "engine": "V8",
                    "engine_version": "12.4",
                    "release_date": "2024-04-24",
                    "release_notes": "https://nodejs.org/en/blog/release/v22.0.0",
                    "status": "esr"
                },
                "23.0.0": {
                    "engine": "V8",
                    "engine_version": "12.9",
                    "release_date": "2024-10-16",
                    "release_notes": "https://nodejs.org/en/blog/release/v23.0.0",
                    "status": "current"
                }
            },
            "type": "server"
        },
        "oculus": {
            "accepts_flags": true,
            "accepts_webextensions": false,
            "name": "Quest Browser",
            "pref_url": "chrome://flags",
            "releases": {
                "23.0": {
                    "engine": "Blink",
                    "engine_version": "104",
                    "release_date": "2022-08-15",
                    "status": "current"
                }
            },
            "type": "xr",
            "upstream": "chrome_android"
        },
        "opera": {
            "accepts_flags": true,
            "accepts_webextensions": true,
            "name": "Opera",
            "pref_url": "opera://flags",
            "releases": {
                "113": {
                    "engine": "Blink",
                    "engine_version": "127",
                    "release_date": "2024-08-22",
                    "release_notes": "https://blogs.opera.com/desktop/2024/08/opera-113/",
                    "status": "current"
                },
                "114": {
                    "engine": "Blink",
                    "engine_version": "128",
                    "status": "beta"
                },
                "115": {
                    "engine": "Blink",
                    "engine_version": "129",
                    "status": "nightly"
                }
            },
            "type": "desktop",
            "upstream": "chrome"
        },
        "opera_android": {
            "accepts_flags": false,
            "accepts_webextensions": false,
            "name": "Opera Android",
            "releases": {
                "84": {
                    "engine": "Blink",
                    "engine_version": "127",
                    "release_date": "2024-08-26",
                    "status": "current"
                }
            },
            "type": "mobile",
            "upstream": "chrome_android"
        },
        "safari": {
            "accepts_flags": true,
            "accepts_webextensions": true,
            "name": "Safari",
            "preview_name": "TP",
            "releases": {
                "18.1": {
                    "engine": "WebKit",
                    "engine_version": "619.2.8",
                    "release_date": "2024-10-28",
                    "release_notes": "https://developer.apple.com/documentation/safari-release-notes/safari-18_1-release-notes",
                    "status": "current"
                },
                "18.2": {
                    "engine": "WebKit",
                    "engine_version": "620.1.11",
                    "release_notes": "https://developer.apple.com/documentation/safari-release-notes/safari-18_2-release-notes",
                    "status": "beta"
                }
            },
            "type": "desktop"
        },
        "safari_ios": {
            "accepts_flags": true,
            "accepts_webextensions": true,
            "name": "Safari on iOS",
            "releases": {
                "18.1": {
                    "engine": "WebKit",
                    "engine_version": "619.2.8",
                    "release_date": "2024-10-28",
                    "release_notes": "https://developer.apple.com/documentation/safari-release-notes/safari-18_1-release-notes",
                    "status": "current"
                },
                "18.2": {
                    "engine": "WebKit",
                    "engine_version": "620.1.11",
                    "release_notes": "https://developer.apple.com/documentation/safari-release-notes/safari-18_2-release-notes",
                    "status": "beta"
                }
            },
            "type": "mobile",
            "upstream": "safari"
        },
        "samsunginternet_android": {
            "accepts_flags": false,
            "accepts_webextensions": false,
            "name": "Samsung Internet",
            "releases": {
                "26.0": {
                    "engine": "Blink",
                    "engine_version": "122",
                    "release_date": "2024-06-07",
                    "status": "current"
                },
                "27.0": {
                    "engine": "Blink",
                    "engine_version": "125",
                    "status": "beta"
                }
            },
            "type": "mobile",
            "upstream": "chrome_android"
        },
        "webview_android": {
            "accepts_flags": false,
            "accepts_webextensions": false,
            "name": "WebView Android",
            "releases": {
                "131": {
                    "engine": "Blink",
                    "engine_version": "131",
                    "release_date": "2024-11-12",
                    "status": "current"
                },
                "132": {
                    "engine": "Blink",
                    "engine_version": "132",
                    "release_date": "2025-01-14",
                    "status": "beta"
                },
                "133": {
                    "engine": "Blink",
                    "engine_version": "133",
                    "release_date": "2025-02-04",
                    "status": "nightly"
                },
                "134": {
                    "engine": "Blink",
                    "engine_version": "134",
                    "status": "planned"
                }
            },
            "type": "mobile",
            "upstream": "chrome_android"
        },
        "webview_ios": {
            "accepts_flags": false,
            "accepts_webextensions": false,
            "name": "WebView on iOS",
            "releases": {
                "18.1": {
                    "engine": "WebKit",
                    "engine_version": "619.2.8",
                    "release_date": "2024-10-28",
                    "release_notes": "https://developer.apple.com/documentation/safari-release-notes/safari-18_1-release-notes",
                    "status": "current"
                },
                "18.2": {
                    "engine": "WebKit",
                    "engine_version": "620.1.11",
                    "release_notes": "https://developer.apple.com/documentation/safari-release-notes/safari-18_2-release-notes",
                    "status": "beta"
                }
            },
            "type": "mobile",
            "upstream": "safari_ios"
        }
    }
}

assistant (Nov 10, 2024, 06:14 PM)

I'll help modify the HTML to show detailed information for each API feature and add URL hash handling. Here's how we'll do this:

create Enhanced Browser Compatibility Viewer

<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
* {
  box-sizing: border-box;
}
body {
  font-family: Helvetica, Arial, sans-serif;
  font-size: 16px;
  margin: 2rem 1rem;
  max-width: 800px;
  margin: 0 auto;
}
#autocomplete-container {
  position: relative;
  width: 100%;
}
#search-input {
  width: 100%;
  padding: 10px;
  font-size: 16px;
}
#suggestions {
  position: absolute;
  top: 100%;
  left: 0;
  right: 0;
  max-height: 300px;
  overflow-y: auto;
  border: 1px solid #ddd;
  background: white;
  z-index: 10;
  margin: 0 15px;
}
.suggestion-item {
  padding: 10px;
  cursor: pointer;
}
.suggestion-item:hover, .suggestion-item.selected {
  background-color: #f0f0f0;
}
#timeline {
  position: relative;
  border-left: 2px solid #ccc;
  margin-left: 20px;
  padding-left: 20px;
  margin-top: 2em;
}
.event {
  margin-bottom: 20px;
  position: relative;
}
.event::before {
  content: '';
  position: absolute;
  left: -26px;
  top: 5px;
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: #3b82f6;
}
.event-date {
  font-size: 0.875rem;
  color: #666;
}
.event-browser {
  font-weight: 600;
  color: #1f2937;
}
.event-version {
  color: #059669;
  margin-left: 4px;
}
.error {
  color: #dc2626;
  margin-top: 8px;
  display: none;
  padding: 8px;
  background: #fef2f2;
  border-radius: 4px;
}
.not-supported {
  margin-top: 20px;
  padding: 12px;
  background: #f3f4f6;
  border-radius: 4px;
}
.api-section {
  margin-top: 2rem;
  padding: 1rem;
  border: 1px solid #e5e7eb;
  border-radius: 8px;
}
.api-section h2 {
  margin-top: 0;
  color: #1f2937;
  font-size: 1.5rem;
}
.api-info {
  margin: 1rem 0;
}
.api-info-item {
  margin: 0.5rem 0;
}
.api-info-label {
  font-weight: 600;
  color: #4b5563;
}
.status-indicator {
  display: inline-block;
  padding: 0.25rem 0.5rem;
  border-radius: 4px;
  font-size: 0.875rem;
  margin-right: 0.5rem;
}
.status-experimental {
  background-color: #fef3c7;
  color: #92400e;
}
.status-deprecated {
  background-color: #fee2e2;
  color: #991b1b;
}
.status-standard {
  background-color: #d1fae5;
  color: #065f46;
}
@media (max-width: 600px) {
  body {
    margin: 1rem;
    font-size: 14px;
  }
  #timeline {
    margin-left: 10px;
    padding-left: 10px;
  }
  .event::before {
    left: -16px;
  }
}
</style>
</head>
<body>
<h1>MDN Browser Support Timelines</h1>

<div id="autocomplete-container">
  <input type="text" id="search-input" placeholder="Search files...">
  <div id="suggestions"></div>
</div>

<div id="timeline-section">
  <div id="timeline"></div>
</div>

<div id="api-details"></div>

<script>
let allFiles = [];
let selectedFiles = [];

async function fetchAllFiles(repo, path = 'api', page = 1, allFiles = []) {
  const token = '';
  const headers = token ? { 'Authorization': `token ${token}` } : {};
  
  try {
    const url = `https://api.github.com/repos/${repo}/contents/${path}?page=${page}&per_page=100`;
    const response = await fetch(url, { headers });
    
    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }
    
    const files = await response.json();
    allFiles.push(...files.map(file => file.path));
    
    if (files.length === 100) {
      return fetchAllFiles(repo, path, page + 1, allFiles);
    }
    
    return allFiles;
  } catch (error) {
    console.error('Error fetching files:', error);
    return allFiles;
  }
}

function updateUrlHash(filePath) {
  const cleanPath = filePath.replace(/^api\//, '');
  history.pushState(null, '', `#${cleanPath}`);
}

function getHashPath() {
  const hash = window.location.hash.slice(1);
  return hash ? `api/${hash}` : null;
}

function setupAutocomplete() {
  const searchInput = document.getElementById('search-input');
  const suggestionsContainer = document.getElementById('suggestions');

  searchInput.addEventListener('input', () => {
    const searchTerm = searchInput.value.toLowerCase();
    const filteredFiles = allFiles.filter(file => 
      file.toLowerCase().includes(searchTerm)
    ).slice(0, 20);

    suggestionsContainer.innerHTML = '';

    filteredFiles.forEach((file, index) => {
      const suggestionItem = document.createElement('div');
      suggestionItem.textContent = file;
      suggestionItem.className = 'suggestion-item';
      suggestionItem.setAttribute('data-index', index);

      suggestionItem.addEventListener('click', () => {
        searchInput.value = file;
        suggestionsContainer.innerHTML = '';
        fetchBrowserCompatData(file);
      });

      suggestionsContainer.appendChild(suggestionItem);
    });
  });

  searchInput.addEventListener('keydown', handleKeyboardNavigation);
}

function handleKeyboardNavigation(e) {
  const suggestionsContainer = document.getElementById('suggestions');
  const suggestions = suggestionsContainer.children;
  
  if (e.key === 'ArrowDown' || e.key === 'ArrowUp') {
    e.preventDefault();
    if (suggestions.length > 0) {
      const currentSelected = suggestionsContainer.querySelector('.selected');
      const nextIndex = currentSelected 
        ? Math.min(parseInt(currentSelected.getAttribute('data-index')) + (e.key === 'ArrowDown' ? 1 : -1), suggestions.length - 1)
        : 0;
      
      if (currentSelected) currentSelected.classList.remove('selected');
      suggestions[nextIndex].classList.add('selected');
    }
  } else if (e.key === 'Enter') {
    const selectedItem = suggestionsContainer.querySelector('.selected') || 
                         suggestionsContainer.children[0];
    
    if (selectedItem) {
      e.preventDefault();
      const searchInput = document.getElementById('search-input');
      searchInput.value = selectedItem.textContent;
      suggestionsContainer.innerHTML = '';
      fetchBrowserCompatData(selectedItem.textContent);
    }
  }
}

function renderApiSection(data) {
  const apiDetails = document.getElementById('api-details');
  apiDetails.innerHTML = '';

  // Render main API section
  const mainSection = createApiSection(data.data.__compat, data.query);
  apiDetails.appendChild(mainSection);

  // Render sub-features
  Object.entries(data.data).forEach(([key, value]) => {
    if (key !== '__compat' && value.__compat) {
      const subSection = createApiSection(value.__compat, `${data.query}.${key}`);
      apiDetails.appendChild(subSection);
    }
  });
}

function createApiSection(compat, title) {
  const section = document.createElement('section');
  section.className = 'api-section';

  const heading = document.createElement('h2');
  heading.textContent = title;
  section.appendChild(heading);

  const info = document.createElement('div');
  info.className = 'api-info';

  // Add MDN link if available
  if (compat.mdn_url) {
    const mdnLink = document.createElement('div');
    mdnLink.className = 'api-info-item';
    const mdnUrl = compat.mdn_url.replace('/en-US/docs/Web/API/', 'https://developer.mozilla.org/en-US/docs/Web/API/');
    mdnLink.innerHTML = `<span class="api-info-label">MDN Documentation:</span> <a href="${mdnUrl}" target="_blank">${mdnUrl}</a>`;
    info.appendChild(mdnLink);
  }

  // Add specification link if available
  if (compat.spec_url) {
    const specLink = document.createElement('div');
    specLink.className = 'api-info-item';
    specLink.innerHTML = `<span class="api-info-label">Specification:</span> <a href="${compat.spec_url}" target="_blank">${compat.spec_url}</a>`;
    info.appendChild(specLink);
  }

  // Add status indicators
  if (compat.status) {
    const statusDiv = document.createElement('div');
    statusDiv.className = 'api-info-item';
    
    Object.entries(compat.status).forEach(([key, value]) => {
      if (value) {
        const status = document.createElement('span');
        status.className = `status-indicator status-${key}`;
        status.textContent = key.charAt(0).toUpperCase() + key.slice(1);
        statusDiv.appendChild(status);
      }
    });
    
    info.appendChild(statusDiv);
  }

  section.appendChild(info);

  // Add timeline for this section
  const timelineDiv = document.createElement('div');
  timelineDiv.className = 'timeline';
  const supportData = extractSupportData({ 
    data: { __compat: compat },
    browsers: data.browsers 
  });
  renderTimeline(supportData, timelineDiv);
  section.appendChild(timelineDiv);

  return section;
}

async function fetchBrowserCompatData(filePath) {
  try {
    updateUrlHash(filePath);
    const url = `https://bcd.developer.mozilla.org/bcd/api/v0/current/${filePath.replace('/', '.')}`;
    const response = await fetch(url);
    
    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }
    
    const data = await response.json();
    const timelineData = extractSupportData(data);
    renderTimeline(timelineData);
    renderApiSection(data);
  } catch (error) {
    console.error('Error fetching browser compat data:', error);
    renderErrorMessage(error.message);
  }
}

function extractSupportData(data) {
  const browsers = data.browsers;
  const support = data.data.__compat.support;
        
  const supportData = [];
  const notSupported = [];
        
  for (const [browserName, supportInfo] of Object.entries(support)) {
    if (!supportInfo[0]) continue;
        
    if (supportInfo[0].version_added === false) {
      notSupported.push(browsers[browserName]?.name || browserName);
      continue;
    }
        
    if (!supportInfo[0].version_added || !supportInfo[0].release_date) continue;
        
    supportData.push({
      browser: browsers[browserName]?.name || browserName,
      version: supportInfo[0].version_added,
      date: supportInfo[0].release_date
    });
  }
        
  return {
    supported: supportData.sort((a, b) => new Date(a.date) - new Date(b.date)),
    notSupported
  };
}

function formatDate(dateStr) {
  return new Date(dateStr).toLocaleDateString('en-US', {
    year: 'numeric',
    month: 'long',
    day: 'numeric'
  });
}

function renderTimeline(data, container = document.getElementById('timeline')) {
  container.innerHTML = '';
        
  data.supported.forEach(item => {
    const event = document.createElement('div');
    event.className = 'event';
    event.innerHTML = `
      <div class="event-date">${formatDate(item.date)}</div>
      <div>
        <span class="event-browser">${item.browser}</span>
        <span class="event-version">v${item.version}</span>
      </div>
    `;
    container.appendChild(event);
  });
   
  if (data.notSupported.length > 0) {
    const notSupportedDiv = document.createElement('div');
    notSupportedDiv.className = 'not-supported';
    notSupportedDiv.innerHTML = `
      <strong>Not Supported:</strong> ${data.notSupported.join(', ')}
    `;
    container.appendChild(notSupportedDiv);
  }
}

function renderErrorMessage(message) {
  const timeline = document.getElementById('timeline');
  timeline.innerHTML = `
    <div class="error" style="display: block;">
      Error: ${message}
    </div>
  `;
}

// Initialize the app
async function init() {
  allFiles = await fetchAllFiles('mdn/browser-compat-data', 'api');
  setupAutocomplete();

  // Check for hash in URL and load that file if present
  const hashPath = getHashPath();
  if (hashPath) {
    document.getElementById('search-input').value = hashPath;
    fetchBrowserCompatData(hashPath);
  }
}

// Handle back/forward navigation
window.addEventListener('popstate', () => {
  const hashPath = getHashPath();
  if (hashPath) {
    document.getElementById('search-input').value = hashPath;
    fetchBrowserCompatData(hashPath);
  }
});

init();
</script>
</body>
</html>

I've enhanced the original code with these main changes:

  1. Added URL hash handling:
  • Added updateUrlHash() function to update the URL when
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment