@@ -46,6 +46,61 @@ if (typeof document !== 'undefined') {
4646
4747// Theme listener imported from controllers/theme-controller.js
4848
49+ function setupTabNavigation ( ) {
50+ const tabButtons = document . querySelectorAll ( '.tab-button' ) ;
51+ const tabPanels = document . querySelectorAll ( '.tab-panel' ) ;
52+
53+ // Function to switch tabs
54+ function switchTab ( tabName ) {
55+ // Update buttons
56+ tabButtons . forEach ( btn => {
57+ const isActive = btn . dataset . tab === tabName ;
58+ btn . classList . toggle ( 'active' , isActive ) ;
59+ btn . setAttribute ( 'aria-selected' , isActive ) ;
60+ } ) ;
61+
62+ // Update panels
63+ tabPanels . forEach ( panel => {
64+ panel . classList . toggle ( 'active' , panel . dataset . tab === tabName ) ;
65+ } ) ;
66+
67+ // Save to localStorage
68+ localStorage . setItem ( 'activeTab' , tabName ) ;
69+
70+ // Update URL hash without scrolling
71+ history . replaceState ( null , null , `#${ tabName } ` ) ;
72+ }
73+
74+ // Add click listeners to tab buttons
75+ tabButtons . forEach ( button => {
76+ button . addEventListener ( 'click' , ( ) => {
77+ switchTab ( button . dataset . tab ) ;
78+ } ) ;
79+ } ) ;
80+
81+ // Add click listeners to clickable setup steps
82+ const clickableSetupSteps = document . querySelectorAll ( '.setup-step.clickable' ) ;
83+ clickableSetupSteps . forEach ( step => {
84+ step . addEventListener ( 'click' , ( ) => {
85+ const tabName = step . dataset . tab ;
86+ if ( tabName ) {
87+ switchTab ( tabName ) ;
88+ }
89+ } ) ;
90+ } ) ;
91+
92+ // Initialize active tab from localStorage or URL hash
93+ const hash = window . location . hash . substring ( 1 ) ;
94+ const savedTab = localStorage . getItem ( 'activeTab' ) ;
95+ const initialTab = hash || savedTab || 'setup' ;
96+
97+ // Check if the hash/saved tab is valid
98+ const validTabs = [ 'setup' , 'repositories' , 'filters' , 'preferences' , 'advanced' , 'help' ] ;
99+ const tabToActivate = validTabs . includes ( initialTab ) ? initialTab : 'setup' ;
100+
101+ switchTab ( tabToActivate ) ;
102+ }
103+
49104function handleUrlParameters ( ) {
50105 // Check for URL parameters to enhance user experience
51106 const urlParams = new URLSearchParams ( window . location . search ) ;
@@ -62,21 +117,48 @@ function handleUrlParameters() {
62117 } , 100 ) ;
63118 }
64119
65- // Handle hash navigation to scroll to specific section
120+ // Map old section hashes to tabs for backwards compatibility
121+ const sectionToTabMap = {
122+ 'token' : 'setup' ,
123+ 'repositories' : 'repositories' ,
124+ 'filters' : 'filters' ,
125+ 'feed-management' : 'preferences' ,
126+ 'appearance' : 'preferences' ,
127+ 'interval' : 'preferences' ,
128+ 'snooze' : 'preferences' ,
129+ 'import-export' : 'advanced'
130+ } ;
131+
132+ // Handle hash navigation - if it's an old section hash, switch to appropriate tab
66133 if ( hash ) {
67- setTimeout ( ( ) => {
68- // Extract just the hash part without query parameters
69- const hashParts = hash . split ( '?' ) ;
70- const cleanHash = hashParts [ 0 ] ;
71- const targetSection = document . querySelector ( cleanHash ) ;
72- if ( targetSection ) {
73- targetSection . scrollIntoView ( { behavior : 'smooth' , block : 'start' } ) ;
74- }
75- } , 200 ) ;
134+ const cleanHash = hash . substring ( 1 ) . split ( '?' ) [ 0 ] ;
135+ const targetTab = sectionToTabMap [ cleanHash ] ;
136+
137+ if ( targetTab ) {
138+ // Switch to the tab containing this section
139+ setTimeout ( ( ) => {
140+ const tabButtons = document . querySelectorAll ( '.tab-button' ) ;
141+ const targetButton = Array . from ( tabButtons ) . find ( btn => btn . dataset . tab === targetTab ) ;
142+ if ( targetButton ) {
143+ targetButton . click ( ) ;
144+
145+ // Then scroll to the section within that tab
146+ setTimeout ( ( ) => {
147+ const targetSection = document . getElementById ( cleanHash ) ;
148+ if ( targetSection ) {
149+ targetSection . scrollIntoView ( { behavior : 'smooth' , block : 'start' } ) ;
150+ }
151+ } , 100 ) ;
152+ }
153+ } , 150 ) ;
154+ }
76155 }
77156}
78157
79158function setupEventListeners ( ) {
159+ // Tab navigation
160+ setupTabNavigation ( ) ;
161+
80162 document . getElementById ( 'addRepoBtn' ) . addEventListener ( 'click' , addRepo ) ;
81163 document . getElementById ( 'clearTokenBtn' ) . addEventListener ( 'click' , clearToken ) ;
82164
@@ -234,6 +316,39 @@ function setupEventListeners() {
234316 } ) ;
235317 } ) ;
236318
319+ // Auto-save itemExpiryHours changes
320+ const itemExpiryEnabledCheckbox = document . getElementById ( 'itemExpiryEnabled' ) ;
321+ const itemExpiryHoursInput = document . getElementById ( 'itemExpiryHours' ) ;
322+ const itemExpiryInputRow = document . getElementById ( 'itemExpiryInputRow' ) ;
323+
324+ itemExpiryEnabledCheckbox . addEventListener ( 'change' , async ( e ) => {
325+ const isEnabled = e . target . checked ;
326+ if ( isEnabled ) {
327+ itemExpiryInputRow . style . display = 'block' ;
328+ const hours = parseInt ( itemExpiryHoursInput . value ) || 24 ;
329+ itemExpiryHoursInput . value = hours ;
330+ await chrome . storage . sync . set ( { itemExpiryHours : hours } ) ;
331+ toastManager . info ( `Auto-removal enabled: items older than ${ hours } hours will be removed` ) ;
332+ } else {
333+ itemExpiryInputRow . style . display = 'none' ;
334+ await chrome . storage . sync . set ( { itemExpiryHours : null } ) ;
335+ toastManager . info ( 'Auto-removal disabled' ) ;
336+ }
337+ } ) ;
338+
339+ itemExpiryHoursInput . addEventListener ( 'change' , async ( e ) => {
340+ let hours = parseInt ( e . target . value ) ;
341+ if ( isNaN ( hours ) || hours < 1 ) {
342+ hours = 1 ;
343+ e . target . value = 1 ;
344+ } else if ( hours > 168 ) {
345+ hours = 168 ;
346+ e . target . value = 168 ;
347+ }
348+ await chrome . storage . sync . set ( { itemExpiryHours : hours } ) ;
349+ toastManager . info ( `Auto-removal time changed to ${ hours } hours` ) ;
350+ } ) ;
351+
237352 // Auto-save filter and notification changes
238353 [ 'filterPrs' , 'filterIssues' , 'filterReleases' , 'notifyPrs' , 'notifyIssues' , 'notifyReleases' ] . forEach ( id => {
239354 document . getElementById ( id ) . addEventListener ( 'change' , async ( e ) => {
@@ -327,6 +442,11 @@ function setupEventListeners() {
327442 }
328443 } ) ;
329444
445+ // Data Management buttons
446+ document . getElementById ( 'clearCacheBtn' ) . addEventListener ( 'click' , clearCacheData ) ;
447+ document . getElementById ( 'clearAllDataBtn' ) . addEventListener ( 'click' , clearAllData ) ;
448+ document . getElementById ( 'resetSettingsBtn' ) . addEventListener ( 'click' , resetSettings ) ;
449+
330450}
331451
332452// Panel toggle functionality
@@ -448,7 +568,8 @@ async function loadSettings() {
448568 'snoozeHours' ,
449569 'filters' ,
450570 'notifications' ,
451- 'theme'
571+ 'theme' ,
572+ 'itemExpiryHours'
452573 ] ) ;
453574
454575 const snoozeSettings = await chrome . storage . sync . get ( [ 'snoozedRepos' ] ) ;
@@ -522,6 +643,14 @@ async function loadSettings() {
522643 themeRadio . checked = true ;
523644 }
524645
646+ // Load itemExpiryHours setting
647+ const itemExpiryEnabled = settings . itemExpiryHours !== null && settings . itemExpiryHours !== undefined ;
648+ document . getElementById ( 'itemExpiryEnabled' ) . checked = itemExpiryEnabled ;
649+ if ( itemExpiryEnabled ) {
650+ document . getElementById ( 'itemExpiryHours' ) . value = settings . itemExpiryHours ;
651+ document . getElementById ( 'itemExpiryInputRow' ) . style . display = 'block' ;
652+ }
653+
525654 // Update notification toggle states after loading settings
526655 updateNotificationToggleStates ( ) ;
527656
@@ -874,12 +1003,93 @@ if (typeof module !== 'undefined' && module.exports) {
8741003 formatNumber,
8751004 formatDate : formatDateVerbose , // Export verbose formatter for tests
8761005 exportSettings,
877- handleImportFile
1006+ handleImportFile,
1007+ clearCacheData,
1008+ clearAllData,
1009+ resetSettings
8781010 } ;
8791011}
8801012
8811013// Snooze functions are now imported from controllers/snooze-controller.js
8821014
1015+ // Data Management Functions
1016+ /**
1017+ * Clear cache data (activities only, preserves settings and repos)
1018+ */
1019+ async function clearCacheData ( ) {
1020+ try {
1021+ await setLocalItem ( 'activities' , [ ] ) ;
1022+ await setLocalItem ( 'readItems' , [ ] ) ;
1023+
1024+ // Reset badge count
1025+ chrome . runtime . sendMessage ( { action : 'clearBadge' } ) ;
1026+
1027+ toastManager . success ( 'Cache cleared successfully' ) ;
1028+ } catch ( error ) {
1029+ console . error ( 'Error clearing cache:' , error ) ;
1030+ toastManager . error ( 'Failed to clear cache' ) ;
1031+ }
1032+ }
1033+
1034+ /**
1035+ * Clear all data (activities and activity history, preserves settings and repos)
1036+ */
1037+ async function clearAllData ( ) {
1038+ // Show confirmation dialog
1039+ const confirmed = confirm (
1040+ 'This will clear all cached data and activity history, but preserve your settings and repositories.\n\nAre you sure you want to continue?'
1041+ ) ;
1042+
1043+ if ( ! confirmed ) {
1044+ return ;
1045+ }
1046+
1047+ try {
1048+ // Clear local storage (activities, readItems, collapsedRepos, etc.)
1049+ await chrome . storage . local . clear ( ) ;
1050+
1051+ // Reset badge count
1052+ chrome . runtime . sendMessage ( { action : 'clearBadge' } ) ;
1053+
1054+ toastManager . success ( 'All data cleared successfully' ) ;
1055+ } catch ( error ) {
1056+ console . error ( 'Error clearing all data:' , error ) ;
1057+ toastManager . error ( 'Failed to clear data' ) ;
1058+ }
1059+ }
1060+
1061+ /**
1062+ * Reset all settings to defaults (clears everything)
1063+ */
1064+ async function resetSettings ( ) {
1065+ // Show confirmation dialog
1066+ const confirmed = confirm (
1067+ 'This will reset ALL settings to defaults and clear your GitHub token and repositories.\n\nThis action cannot be undone. Are you sure?'
1068+ ) ;
1069+
1070+ if ( ! confirmed ) {
1071+ return ;
1072+ }
1073+
1074+ try {
1075+ // Clear all storage (both sync and local)
1076+ await chrome . storage . sync . clear ( ) ;
1077+ await chrome . storage . local . clear ( ) ;
1078+
1079+ // Reset badge count
1080+ chrome . runtime . sendMessage ( { action : 'clearBadge' } ) ;
1081+
1082+ toastManager . success ( 'Settings reset to defaults' ) ;
1083+
1084+ // Reload the page to show default state
1085+ setTimeout ( ( ) => {
1086+ window . location . reload ( ) ;
1087+ } , 1000 ) ;
1088+ } catch ( error ) {
1089+ console . error ( 'Error resetting settings:' , error ) ;
1090+ toastManager . error ( 'Failed to reset settings' ) ;
1091+ }
1092+ }
8831093
8841094// Auto-refresh snoozed repos list every 5 minutes
8851095setInterval ( async ( ) => {
@@ -900,5 +1110,8 @@ export {
9001110 formatNumber ,
9011111 formatDateVerbose as formatDate , // Export verbose formatter for tests
9021112 exportSettings ,
903- handleImportFile
1113+ handleImportFile ,
1114+ clearCacheData ,
1115+ clearAllData ,
1116+ resetSettings
9041117} ;
0 commit comments