@@ -8,6 +8,74 @@ import { useDocuments } from '~/contexts/documents';
88import { Logo } from '../logo' ;
99import ThemeToggle from '../theme-toggle' ;
1010import { Button } from '../ui/button' ;
11+ import { X } from '@phosphor-icons/react' ;
12+
13+ const VERSION = '0.1.24' ;
14+ const VERSION_CHECK_INTERVAL = 1000 * 60 * 60 * 24 ; // Check once per day
15+
16+ const compareVersions = ( v1 : string , v2 : string ) : number => {
17+ // Split version string at '-' to separate main version from prerelease (if any)
18+ const [ main1 , pre1 ] = v1 . split ( '-' ) ;
19+ const [ main2 , pre2 ] = v2 . split ( '-' ) ;
20+
21+ // Split main version into major, minor, patch numbers
22+ const parts1 = main1 ?. split ( '.' ) . map ( Number ) ?? [ ] ;
23+ const parts2 = main2 ?. split ( '.' ) . map ( Number ) ?? [ ] ;
24+
25+ // Compare major, minor, and patch values
26+ for ( let i = 0 ; i < Math . max ( parts1 . length , parts2 . length ) ; i ++ ) {
27+ const num1 = parts1 [ i ] || 0 ;
28+ const num2 = parts2 [ i ] || 0 ;
29+ if ( num1 !== num2 ) {
30+ return num1 - num2 ;
31+ }
32+ }
33+
34+ // If the numeric (stable) parts are equal, handle prerelease parts.
35+ // A version without a prerelease is considered greater than one with a prerelease.
36+ if ( pre1 && ! pre2 ) {
37+ return - 1 ;
38+ } else if ( ! pre1 && pre2 ) {
39+ return 1 ;
40+ } else if ( pre1 && pre2 ) {
41+ // Lexicographical comparison for prerelease strings.
42+ if ( pre1 === pre2 ) return 0 ;
43+ return pre1 > pre2 ? 1 : - 1 ;
44+ }
45+
46+ return 0 ;
47+ } ;
48+
49+ const useVersionCheck = ( ) => {
50+ const [ newVersion , setNewVersion ] = React . useState < string | null > ( null ) ;
51+
52+ React . useEffect ( ( ) => {
53+ const checkVersion = async ( ) => {
54+ try {
55+ const response = await fetch ( 'https://registry.npmjs.org/htmldocs/latest' ) ;
56+ const data = await response . json ( ) ;
57+ const latestVersion = data . version ;
58+
59+ // If a latestVersion is available and it is greater than our current version, set it as new.
60+ if ( latestVersion && compareVersions ( latestVersion , VERSION ) > 0 ) {
61+ setNewVersion ( latestVersion ) ;
62+ }
63+ } catch ( error ) {
64+ console . error ( 'Failed to check for new version:' , error ) ;
65+ }
66+ } ;
67+
68+ // Check immediately
69+ checkVersion ( ) ;
70+
71+ // Set up interval for periodic checks
72+ const interval = setInterval ( checkVersion , VERSION_CHECK_INTERVAL ) ;
73+
74+ return ( ) => clearInterval ( interval ) ;
75+ } , [ ] ) ;
76+
77+ return newVersion ;
78+ } ;
1179
1280interface SidebarProps {
1381 className ?: string ;
@@ -21,6 +89,8 @@ export const Sidebar = ({
2189 style,
2290} : SidebarProps ) => {
2391 const { documentsDirectoryMetadata } = useDocuments ( ) ;
92+ const newVersion = useVersionCheck ( ) ;
93+ const [ isDismissed , setIsDismissed ] = React . useState ( false ) ;
2494
2595 return (
2696 < aside
@@ -31,7 +101,7 @@ export const Sidebar = ({
31101 < Logo />
32102 < ThemeToggle />
33103 </ div >
34- < nav className = "p-4 flex-grow lg:pt-0 pl-0 w-screen h-[calc(100vh_-_70px)] lg:w-full lg:min-w-[275px] lg:max-w-[275px] flex flex-col overflow-y-auto" >
104+ < nav className = "p-4 flex-grow lg:pt-0 pl-0 w-screen h-[calc(100vh_-_70px)] lg:w-full lg:min-w-[275px] lg:max-w-[275px] flex flex-col overflow-y-auto justify-between " >
35105 < Collapsible . Root >
36106 < React . Suspense >
37107 < SidebarDirectoryChildren
@@ -42,6 +112,35 @@ export const Sidebar = ({
42112 />
43113 </ React . Suspense >
44114 </ Collapsible . Root >
115+ { newVersion && ! isDismissed && (
116+ < div className = "ml-4 p-2 bg-secondary flex items-center justify-between border border-border rounded-md" >
117+ < div >
118+ < p className = "text-xs text-foreground" >
119+ New version (
120+ < a
121+ href = "https://www.npmjs.com/package/htmldocs"
122+ target = "_blank"
123+ rel = "noopener noreferrer"
124+ className = "hover:underline"
125+ >
126+ { newVersion }
127+ </ a >
128+ ) available
129+ </ p >
130+ < p className = "text-xs text-muted-foreground" >
131+ npm install -g htmldocs@latest
132+ </ p >
133+ </ div >
134+ < Button
135+ variant = "ghost"
136+ size = "icon"
137+ className = "h-6 w-6"
138+ onClick = { ( ) => setIsDismissed ( true ) }
139+ >
140+ < X className = "h-4 w-4" />
141+ </ Button >
142+ </ div >
143+ ) }
45144 </ nav >
46145 </ aside >
47146 ) ;
0 commit comments