Skip to content

Commit 472b46f

Browse files
authored
1 parent 59323c6 commit 472b46f

File tree

1 file changed

+184
-54
lines changed

1 file changed

+184
-54
lines changed

mdn-timelines.html

+184-54
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@
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;
@@ -88,6 +88,46 @@
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;
@@ -104,7 +144,6 @@
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>
120161
let allFiles = [];
121162
let selectedFiles = [];
122163

123164
async 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(/^api\//, '');
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+
152200
function 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+
221337
async 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

238357
function 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

305420
function 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>
315430
async 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+
320451
init();
321452
</script>
322453
</body>
323-
</body>
324454
</html>

0 commit comments

Comments
 (0)