@@ -54,6 +54,18 @@ const statusConfig: Record<LLMStatus, { text: string; color: string; tooltip: st
5454 } ,
5555} ;
5656
57+ type TimestampResult = { relative : string ; absolute : string } | { fallback : string } ;
58+
59+ const RELATIVE_TIME_DIVISIONS : { amount : number ; unit : Intl . RelativeTimeFormatUnit } [ ] = [
60+ { amount : 60 , unit : 'second' } ,
61+ { amount : 60 , unit : 'minute' } ,
62+ { amount : 24 , unit : 'hour' } ,
63+ { amount : 7 , unit : 'day' } ,
64+ { amount : 4.34524 , unit : 'week' } ,
65+ { amount : 12 , unit : 'month' } ,
66+ { amount : Number . POSITIVE_INFINITY , unit : 'year' } ,
67+ ] ;
68+
5769interface ZoomButtonProps extends Omit < React . ButtonHTMLAttributes < HTMLButtonElement > , 'children' > {
5870 hint : string ;
5971 icon : React . ReactNode ;
@@ -185,14 +197,71 @@ const StatusBar: React.FC<StatusBarProps> = ({
185197 onDatabaseMenu ( event ) ;
186198 } ;
187199
188- const formatTimestamp = ( isoString ?: string ) => {
189- if ( ! isoString ) return 'Not saved yet' ;
190- try {
191- return new Date ( isoString ) . toLocaleTimeString ( ) ;
192- } catch {
193- return 'Invalid date' ;
200+ const formatTimestamp = React . useCallback ( ( isoString ?: string ) : TimestampResult => {
201+ if ( ! isoString ) {
202+ return { fallback : 'Not saved yet' } ;
194203 }
195- } ;
204+
205+ const date = new Date ( isoString ) ;
206+
207+ if ( Number . isNaN ( date . getTime ( ) ) ) {
208+ return { fallback : 'Invalid date' } ;
209+ }
210+
211+ const absolute = date . toLocaleString ( ) ;
212+ const formatter = new Intl . RelativeTimeFormat ( undefined , { numeric : 'auto' } ) ;
213+ let duration = Math . round ( ( date . getTime ( ) - Date . now ( ) ) / 1000 ) ;
214+
215+ for ( const division of RELATIVE_TIME_DIVISIONS ) {
216+ if ( Math . abs ( duration ) < division . amount ) {
217+ return { relative : formatter . format ( duration , division . unit ) , absolute } ;
218+ }
219+ duration = Math . round ( duration / division . amount ) ;
220+ }
221+
222+ return { fallback : 'Invalid date' } ;
223+ } , [ ] ) ;
224+
225+ const [ lastSavedDisplay , setLastSavedDisplay ] = React . useState < { relative : string ; absolute : string } | null > ( ( ) => {
226+ const result = formatTimestamp ( lastSaved ) ;
227+ return 'fallback' in result ? null : result ;
228+ } ) ;
229+ const [ lastSavedFallback , setLastSavedFallback ] = React . useState < string | null > ( ( ) => {
230+ const result = formatTimestamp ( lastSaved ) ;
231+ return 'fallback' in result ? result . fallback : null ;
232+ } ) ;
233+ const lastSavedTriggerRef = React . useRef < HTMLSpanElement > ( null ) ;
234+ const [ showLastSavedTooltip , setShowLastSavedTooltip ] = React . useState ( false ) ;
235+
236+ React . useEffect ( ( ) => {
237+ let intervalId : number | undefined ;
238+
239+ const updateTimestamp = ( ) => {
240+ const result = formatTimestamp ( lastSaved ) ;
241+ if ( 'fallback' in result ) {
242+ setLastSavedDisplay ( null ) ;
243+ setLastSavedFallback ( result . fallback ) ;
244+ setShowLastSavedTooltip ( false ) ;
245+ return false ;
246+ }
247+
248+ setLastSavedFallback ( null ) ;
249+ setLastSavedDisplay ( result ) ;
250+ return true ;
251+ } ;
252+
253+ const hasValidTimestamp = updateTimestamp ( ) ;
254+
255+ if ( hasValidTimestamp && typeof window !== 'undefined' ) {
256+ intervalId = window . setInterval ( updateTimestamp , 60000 ) ;
257+ }
258+
259+ return ( ) => {
260+ if ( intervalId !== undefined && typeof window !== 'undefined' ) {
261+ window . clearInterval ( intervalId ) ;
262+ }
263+ } ;
264+ } , [ lastSaved , formatTimestamp ] ) ;
196265
197266 const selectStyles : React . CSSProperties = {
198267 maxWidth : '160px' ,
@@ -392,7 +461,30 @@ const StatusBar: React.FC<StatusBarProps> = ({
392461 < div className = "h-4 w-px bg-border-color" > </ div >
393462 < span > Documents: < span className = "font-semibold text-text-main" > { documentCount } </ span > </ span >
394463 < div className = "h-4 w-px bg-border-color" > </ div >
395- < span > Last Saved: < span className = "font-semibold text-text-main" > { formatTimestamp ( lastSaved ) } </ span > </ span >
464+ < span className = "flex items-center gap-1" >
465+ Last Saved:
466+ < span
467+ ref = { lastSavedTriggerRef }
468+ className = "font-semibold text-text-main"
469+ onMouseEnter = { ( ) => lastSavedDisplay && setShowLastSavedTooltip ( true ) }
470+ onMouseLeave = { ( ) => setShowLastSavedTooltip ( false ) }
471+ onFocus = { ( ) => lastSavedDisplay && setShowLastSavedTooltip ( true ) }
472+ onBlur = { ( ) => setShowLastSavedTooltip ( false ) }
473+ tabIndex = { lastSavedDisplay ? 0 : undefined }
474+ >
475+ { lastSavedFallback ?? lastSavedDisplay ?. relative ?? 'Not saved yet' }
476+ </ span >
477+ </ span >
478+ { lastSavedDisplay && showLastSavedTooltip && lastSavedTriggerRef . current && (
479+ < Tooltip
480+ targetRef = { lastSavedTriggerRef }
481+ content = { (
482+ < span className = "block whitespace-pre-line break-words leading-snug text-left" >
483+ { lastSavedDisplay . absolute }
484+ </ span >
485+ ) }
486+ />
487+ ) }
396488 { appVersion && < div className = "h-4 w-px bg-border-color" > </ div > }
397489 { appVersion && (
398490 onOpenAbout ? (
0 commit comments