@@ -8,6 +8,74 @@ import { useDocuments } from '~/contexts/documents';
8
8
import { Logo } from '../logo' ;
9
9
import ThemeToggle from '../theme-toggle' ;
10
10
import { 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
+ } ;
11
79
12
80
interface SidebarProps {
13
81
className ?: string ;
@@ -21,6 +89,8 @@ export const Sidebar = ({
21
89
style,
22
90
} : SidebarProps ) => {
23
91
const { documentsDirectoryMetadata } = useDocuments ( ) ;
92
+ const newVersion = useVersionCheck ( ) ;
93
+ const [ isDismissed , setIsDismissed ] = React . useState ( false ) ;
24
94
25
95
return (
26
96
< aside
@@ -31,7 +101,7 @@ export const Sidebar = ({
31
101
< Logo />
32
102
< ThemeToggle />
33
103
</ 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 " >
35
105
< Collapsible . Root >
36
106
< React . Suspense >
37
107
< SidebarDirectoryChildren
@@ -42,6 +112,35 @@ export const Sidebar = ({
42
112
/>
43
113
</ React . Suspense >
44
114
</ 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
+ ) }
45
144
</ nav >
46
145
</ aside >
47
146
) ;
0 commit comments