11import { browsers , features } from './data.js' ;
22import { browserIcons } from './browser-icons.js' ;
33import { downloadICal } from './ical-generator.js' ;
4+ import { fetchDeveloperSignals } from './developer-signals.js' ;
45
56class TimelineApp {
67 constructor ( ) {
78 this . timelineContent = document . querySelector ( '.timeline-content' ) ;
8- this . features = this . processFeatures ( ) ;
9+ this . developerSignals = null ; // Will be populated after fetch
10+ this . features = [ ] ;
911 this . selectedFeatures = new Set ( ) ; // Track selected features
1012 this . init ( ) ;
1113 }
1214
15+ async init ( ) {
16+ try {
17+ // Fetch developer signals data
18+ this . developerSignals = await fetchDeveloperSignals ( ) ;
19+
20+ // Process features after getting developer signals
21+ this . features = this . processFeatures ( ) ;
22+
23+ // Render the timeline
24+ this . renderTimeline ( ) ;
25+
26+ // Initialize event listeners
27+ this . initEventListeners ( ) ;
28+ } catch ( error ) {
29+ console . error ( 'Error initializing timeline:' , error ) ;
30+ }
31+ }
32+
1333 processFeatures ( ) {
1434 const processedFeatures = [ ] ;
1535
1636 Object . entries ( features )
1737 . forEach ( ( [ id , data ] ) => {
38+ // Skip non-feature kinds (moved or split)
39+ if ( data . kind && data . kind !== 'feature' ) {
40+ return ;
41+ }
42+
1843 // Get all ship dates from browsers
1944 const shipDates = Object . entries ( data . status ?. support || { } )
2045 . map ( ( [ browser , version ] ) => {
@@ -58,7 +83,9 @@ class TimelineApp {
5883 description_html : data . description_html || data . description ,
5984 spec : data . spec ,
6085 status : data . status ,
61- shipDates : shipDates
86+ shipDates : shipDates ,
87+ // Add developer signals data if available
88+ developerSignal : this . developerSignals ?. [ id ] || null
6289 } ;
6390
6491 // Get current date for comparison
@@ -261,12 +288,42 @@ class TimelineApp {
261288 const nameText = document . createTextNode ( feature . name ) ;
262289 title . appendChild ( nameText ) ;
263290
291+ // Add upvote info next to the feature name if available
292+ if ( feature . developerSignal ) {
293+ const upvoteInfo = document . createElement ( 'div' ) ;
294+ upvoteInfo . className = 'upvote-info title-upvote' ;
295+
296+ // Create a linked upvote count (the entire element is clickable)
297+ const upvoteLink = document . createElement ( 'a' ) ;
298+ upvoteLink . href = feature . developerSignal . url ;
299+ upvoteLink . className = 'upvote-count' ;
300+ upvoteLink . target = '_blank' ;
301+ upvoteLink . rel = 'noopener noreferrer' ;
302+ upvoteLink . innerHTML = `<span class="upvote-icon">👍</span>${ feature . developerSignal . votes } ` ;
303+ upvoteLink . title = 'Add your support for this feature on GitHub' ;
304+
305+ // Add event to prevent card selection when clicking the button
306+ upvoteLink . addEventListener ( 'click' , ( e ) => {
307+ e . stopPropagation ( ) ;
308+ } ) ;
309+
310+ upvoteInfo . appendChild ( upvoteLink ) ;
311+ title . appendChild ( upvoteInfo ) ;
312+ }
313+
264314 // Add a link icon for deep linking to this feature
265315 const linkIcon = document . createElement ( 'a' ) ;
266316 linkIcon . href = `#${ uniqueCardId } ` ;
267317 linkIcon . className = 'feature-link-icon' ;
268318 linkIcon . innerHTML = '🔗' ;
269- linkIcon . title = `Link to ${ feature . name } (${ feature . displayType } )` ;
319+
320+ // Use the correct display type in tooltip - check if it's a limited availability feature
321+ let displayTypeForTooltip = feature . displayType ;
322+ if ( feature . status ?. baseline === false ) {
323+ displayTypeForTooltip = 'limited-availability' ;
324+ }
325+ linkIcon . title = `Link to ${ feature . name } (${ displayTypeForTooltip } )` ;
326+
270327 linkIcon . style . fontSize = '0.8em' ;
271328 linkIcon . style . marginLeft = '0.5em' ;
272329 linkIcon . style . opacity = '0' ;
@@ -617,7 +674,12 @@ class TimelineApp {
617674
618675 // Create a unique ID that includes both the feature name and its display type
619676 // This ensures each instance (newly vs widely available) has a unique ID
620- const uniqueCardId = `feature-${ feature . id } -${ feature . displayType } ` ;
677+ let cardType = feature . displayType ;
678+ // Use 'limited-availability' in the ID for limited availability features instead of 'newly-available'
679+ if ( feature . status ?. baseline === false ) {
680+ cardType = 'limited-availability' ;
681+ }
682+ const uniqueCardId = `feature-${ feature . id } -${ cardType } ` ;
621683
622684 // Add a unique ID to the feature card for deep linking
623685 card . id = uniqueCardId ;
@@ -725,7 +787,7 @@ class TimelineApp {
725787 } ) ;
726788 }
727789
728- init ( ) {
790+ initEventListeners ( ) {
729791 // Add event listeners for iCal download buttons
730792 const downloadTopBtn = document . getElementById ( 'download-ical-top' ) ;
731793 const downloadBottomBtn = document . getElementById ( 'download-ical-bottom' ) ;
@@ -735,13 +797,18 @@ class TimelineApp {
735797 downloadICal ( this . getSelectedFeatures ( ) ) ;
736798 } ) ;
737799 }
738-
800+
739801 if ( downloadBottomBtn ) {
740802 downloadBottomBtn . addEventListener ( 'click' , ( ) => {
741803 downloadICal ( this . getSelectedFeatures ( ) ) ;
742804 } ) ;
743805 }
806+ }
744807
808+ renderTimeline ( ) {
809+ // Clear existing content
810+ this . timelineContent . innerHTML = '' ;
811+
745812 const groups = this . groupFeaturesByDate ( ) ;
746813
747814 // Find the current month for scrolling
0 commit comments