@@ -308,6 +308,50 @@ const ZOOM_MAX = 200;
308308const ZOOM_STEP = 10 ;
309309let zoomLevel = 100 ; // Percentage (100 = default)
310310
311+ // User settings (persisted to localStorage)
312+ interface UserSettings {
313+ scrollSpeed : number ; // 10-100, percentage
314+ defaultFontSize : number ; // 10-20, pixels
315+ defaultGapThreshold : number ; // 1-60, seconds
316+ autoAnalyze : boolean ;
317+ }
318+
319+ const DEFAULT_SETTINGS : UserSettings = {
320+ scrollSpeed : 30 ,
321+ defaultFontSize : 13 ,
322+ defaultGapThreshold : 5 ,
323+ autoAnalyze : false
324+ } ;
325+
326+ let userSettings : UserSettings = { ...DEFAULT_SETTINGS } ;
327+
328+ function loadSettings ( ) : void {
329+ try {
330+ const saved = localStorage . getItem ( 'logan-settings' ) ;
331+ if ( saved ) {
332+ userSettings = { ...DEFAULT_SETTINGS , ...JSON . parse ( saved ) } ;
333+ }
334+ } catch {
335+ userSettings = { ...DEFAULT_SETTINGS } ;
336+ }
337+ }
338+
339+ function saveSettings ( ) : void {
340+ localStorage . setItem ( 'logan-settings' , JSON . stringify ( userSettings ) ) ;
341+ }
342+
343+ function applySettings ( ) : void {
344+ // Apply default font size to zoom level
345+ const fontSizeRatio = userSettings . defaultFontSize / BASE_FONT_SIZE ;
346+ zoomLevel = Math . round ( fontSizeRatio * 100 ) ;
347+
348+ // Update gap threshold input if it exists
349+ const gapInput = document . getElementById ( 'gap-threshold' ) as HTMLInputElement ;
350+ if ( gapInput ) {
351+ gapInput . value = userSettings . defaultGapThreshold . toString ( ) ;
352+ }
353+ }
354+
311355// Word wrap setting
312356let wordWrapEnabled = false ;
313357
@@ -435,6 +479,18 @@ const elements = {
435479 btnHelp : document . getElementById ( 'btn-help' ) as HTMLButtonElement ,
436480 helpModal : document . getElementById ( 'help-modal' ) as HTMLDivElement ,
437481 btnCloseHelp : document . getElementById ( 'btn-close-help' ) as HTMLButtonElement ,
482+ // Settings
483+ btnSettings : document . getElementById ( 'btn-settings' ) as HTMLButtonElement ,
484+ settingsModal : document . getElementById ( 'settings-modal' ) as HTMLDivElement ,
485+ scrollSpeedSlider : document . getElementById ( 'scroll-speed' ) as HTMLInputElement ,
486+ scrollSpeedValue : document . getElementById ( 'scroll-speed-value' ) as HTMLSpanElement ,
487+ defaultFontSizeSlider : document . getElementById ( 'default-font-size' ) as HTMLInputElement ,
488+ defaultFontSizeValue : document . getElementById ( 'default-font-size-value' ) as HTMLSpanElement ,
489+ defaultGapThresholdSlider : document . getElementById ( 'default-gap-threshold' ) as HTMLInputElement ,
490+ defaultGapThresholdValue : document . getElementById ( 'default-gap-threshold-value' ) as HTMLSpanElement ,
491+ autoAnalyzeCheckbox : document . getElementById ( 'auto-analyze' ) as HTMLInputElement ,
492+ btnResetSettings : document . getElementById ( 'btn-reset-settings' ) as HTMLButtonElement ,
493+ btnCloseSettings : document . getElementById ( 'btn-close-settings' ) as HTMLButtonElement ,
438494 // Bookmark modal
439495 bookmarkModal : document . getElementById ( 'bookmark-modal' ) as HTMLDivElement ,
440496 bookmarkModalTitle : document . getElementById ( 'bookmark-modal-title' ) as HTMLHeadingElement ,
@@ -695,9 +751,8 @@ function createLogViewer(): void {
695751 let delta = e . deltaY ;
696752
697753 if ( e . deltaMode === 0 ) {
698- // Pixel-based scrolling (trackpad) - reduce speed significantly
699- // Trackpads send high-frequency small deltas, reduce by ~3x
700- delta = delta * 0.3 ;
754+ // Pixel-based scrolling (trackpad) - use user's scroll speed setting
755+ delta = delta * ( userSettings . scrollSpeed / 100 ) ;
701756 } else if ( e . deltaMode === 1 ) {
702757 // Line-based scrolling (mouse wheel) - convert to pixels
703758 delta = delta * getLineHeight ( ) ;
@@ -2635,6 +2690,12 @@ async function loadFile(filePath: string, createNewTab: boolean = true): Promise
26352690 updateProgress ( percent ) ;
26362691 updateProgressText ( `Building minimap... ${ percent } %` ) ;
26372692 } ) ;
2693+
2694+ // Auto-analyze if enabled in settings
2695+ if ( userSettings . autoAnalyze && ! isMarkdownFile ) {
2696+ hideProgress ( ) ;
2697+ await analyzeFile ( ) ;
2698+ }
26382699 } else {
26392700 alert ( `Failed to open file: ${ result . error } ` ) ;
26402701 }
@@ -4975,6 +5036,10 @@ async function checkSearchEngine(): Promise<void> {
49755036const GITHUB_URL = 'https://github.com/SolidKeyAB/logan' ;
49765037
49775038function init ( ) : void {
5039+ // Load user settings from localStorage
5040+ loadSettings ( ) ;
5041+ applySettings ( ) ;
5042+
49785043 // Check search engine on startup
49795044 checkSearchEngine ( ) ;
49805045
@@ -5134,6 +5199,67 @@ function init(): void {
51345199 elements . helpModal . classList . add ( 'hidden' ) ;
51355200 } ) ;
51365201
5202+ // Settings modal
5203+ elements . btnSettings . addEventListener ( 'click' , ( ) => {
5204+ // Load current settings into UI
5205+ elements . scrollSpeedSlider . value = userSettings . scrollSpeed . toString ( ) ;
5206+ elements . scrollSpeedValue . textContent = `${ userSettings . scrollSpeed } %` ;
5207+ elements . defaultFontSizeSlider . value = userSettings . defaultFontSize . toString ( ) ;
5208+ elements . defaultFontSizeValue . textContent = `${ userSettings . defaultFontSize } px` ;
5209+ elements . defaultGapThresholdSlider . value = userSettings . defaultGapThreshold . toString ( ) ;
5210+ elements . defaultGapThresholdValue . textContent = `${ userSettings . defaultGapThreshold } s` ;
5211+ elements . autoAnalyzeCheckbox . checked = userSettings . autoAnalyze ;
5212+ elements . settingsModal . classList . remove ( 'hidden' ) ;
5213+ } ) ;
5214+
5215+ elements . scrollSpeedSlider . addEventListener ( 'input' , ( ) => {
5216+ const value = parseInt ( elements . scrollSpeedSlider . value , 10 ) ;
5217+ elements . scrollSpeedValue . textContent = `${ value } %` ;
5218+ userSettings . scrollSpeed = value ;
5219+ saveSettings ( ) ;
5220+ } ) ;
5221+
5222+ elements . defaultFontSizeSlider . addEventListener ( 'input' , ( ) => {
5223+ const value = parseInt ( elements . defaultFontSizeSlider . value , 10 ) ;
5224+ elements . defaultFontSizeValue . textContent = `${ value } px` ;
5225+ userSettings . defaultFontSize = value ;
5226+ saveSettings ( ) ;
5227+ } ) ;
5228+
5229+ elements . defaultGapThresholdSlider . addEventListener ( 'input' , ( ) => {
5230+ const value = parseInt ( elements . defaultGapThresholdSlider . value , 10 ) ;
5231+ elements . defaultGapThresholdValue . textContent = `${ value } s` ;
5232+ userSettings . defaultGapThreshold = value ;
5233+ saveSettings ( ) ;
5234+ // Update the gap threshold input if visible
5235+ const gapInput = document . getElementById ( 'gap-threshold' ) as HTMLInputElement ;
5236+ if ( gapInput ) {
5237+ gapInput . value = value . toString ( ) ;
5238+ }
5239+ } ) ;
5240+
5241+ elements . autoAnalyzeCheckbox . addEventListener ( 'change' , ( ) => {
5242+ userSettings . autoAnalyze = elements . autoAnalyzeCheckbox . checked ;
5243+ saveSettings ( ) ;
5244+ } ) ;
5245+
5246+ elements . btnResetSettings . addEventListener ( 'click' , ( ) => {
5247+ userSettings = { ...DEFAULT_SETTINGS } ;
5248+ saveSettings ( ) ;
5249+ // Update UI
5250+ elements . scrollSpeedSlider . value = userSettings . scrollSpeed . toString ( ) ;
5251+ elements . scrollSpeedValue . textContent = `${ userSettings . scrollSpeed } %` ;
5252+ elements . defaultFontSizeSlider . value = userSettings . defaultFontSize . toString ( ) ;
5253+ elements . defaultFontSizeValue . textContent = `${ userSettings . defaultFontSize } px` ;
5254+ elements . defaultGapThresholdSlider . value = userSettings . defaultGapThreshold . toString ( ) ;
5255+ elements . defaultGapThresholdValue . textContent = `${ userSettings . defaultGapThreshold } s` ;
5256+ elements . autoAnalyzeCheckbox . checked = userSettings . autoAnalyze ;
5257+ } ) ;
5258+
5259+ elements . btnCloseSettings . addEventListener ( 'click' , ( ) => {
5260+ elements . settingsModal . classList . add ( 'hidden' ) ;
5261+ } ) ;
5262+
51375263 // Bookmark modal
51385264 elements . btnSaveBookmark . addEventListener ( 'click' , ( ) => hideBookmarkModal ( true ) ) ;
51395265 elements . btnCancelBookmark . addEventListener ( 'click' , ( ) => hideBookmarkModal ( false ) ) ;
0 commit comments