@@ -9,6 +9,7 @@ interface ChildrenProps {
9
9
}
10
10
11
11
const AUTHJS_TAB_KEY = "authjs.codeTab.framework"
12
+ const AUTHJS_TAB_KEY_ALL = "authjs.codeTab.framework.all"
12
13
13
14
Code . Next = NextCode
14
15
Code . NextClient = NextClientCode
@@ -34,66 +35,162 @@ const allFrameworks = {
34
35
[ ExpressCode . name ] : "Express" ,
35
36
}
36
37
37
- /**
38
- * Replace all non-alphabetic characters with a hyphen
39
- *
40
- * @param url - URL to parse
41
- * @returns - A string parsed from the URL
42
- */
43
- const parseParams = ( url : string ) : string => {
44
- let parsedUrl = url . toLowerCase ( ) . replaceAll ( / [ ^ a - z A - z ] + / g, "-" )
45
- return parsedUrl . endsWith ( "-" ) ? parsedUrl . slice ( 0 , - 1 ) : parsedUrl
38
+ const findFrameworkKey = (
39
+ text : string ,
40
+ frameworks : Record < string , string >
41
+ ) : string | null => {
42
+ const entry = Object . entries ( frameworks ) . find ( ( [ _ , value ] ) => value === text )
43
+ return entry ? entry [ 0 ] : null
44
+ }
45
+
46
+ const getIndexFrameworkFromUrl = (
47
+ url : string ,
48
+ frameworks : Record < string , string >
49
+ ) : number | null => {
50
+ const params = new URLSearchParams ( url )
51
+ const paramValue = params . get ( "framework" )
52
+ if ( ! paramValue ) return null
53
+
54
+ const decodedValue = decodeURI ( paramValue )
55
+
56
+ const index = Object . values ( frameworks ) . findIndex (
57
+ ( value ) => value === decodedValue
58
+ )
59
+ return index === - 1 ? null : index
60
+ }
61
+
62
+ const getIndexFrameworkFromStorage = (
63
+ frameworks : Record < string , string > ,
64
+ isAllFrameworks : boolean
65
+ ) : number | null => {
66
+ const storageKey = isAllFrameworks ? AUTHJS_TAB_KEY_ALL : AUTHJS_TAB_KEY
67
+ const storedIndex = window . localStorage . getItem ( storageKey )
68
+
69
+ if ( ! storedIndex ) {
70
+ return null
71
+ }
72
+
73
+ return parseInt ( storedIndex ) % Object . keys ( frameworks ) . length
74
+ }
75
+
76
+ const updateFrameworkStorage = (
77
+ frameworkURI : string ,
78
+ frameworks : Record < string , string > ,
79
+ isAllFrameworks : boolean
80
+ ) : void => {
81
+ const index = Object . values ( frameworks ) . findIndex (
82
+ ( value ) => encodeURI ( value ) === frameworkURI
83
+ )
84
+ if ( index === - 1 ) return
85
+
86
+ const storageKey = isAllFrameworks ? AUTHJS_TAB_KEY_ALL : AUTHJS_TAB_KEY
87
+ window . localStorage . setItem ( storageKey , index . toString ( ) )
88
+
89
+ // Update other storage if framework exists in other object
90
+ const otherFrameworksValues = Object . values (
91
+ isAllFrameworks ? baseFrameworks : allFrameworks
92
+ )
93
+ const otherStorageKey = isAllFrameworks ? AUTHJS_TAB_KEY : AUTHJS_TAB_KEY_ALL
94
+
95
+ const encodedFrameworksValues = otherFrameworksValues . map ( ( value ) =>
96
+ encodeURI ( value )
97
+ )
98
+ const existsInOther = encodedFrameworksValues . some (
99
+ ( encodedFramework ) => encodedFramework === frameworkURI
100
+ )
101
+ if ( existsInOther ) {
102
+ const otherIndex = otherFrameworksValues . findIndex (
103
+ ( encodedFramework ) => encodedFramework === frameworkURI
104
+ )
105
+ window . localStorage . setItem ( otherStorageKey , otherIndex . toString ( ) )
106
+ // see https://github.com/shuding/nextra/blob/7ae958f02922e608151411042f658480b75164a6/packages/nextra/src/client/components/tabs/index.client.tsx#L106
107
+ window . dispatchEvent (
108
+ new StorageEvent ( "storage" , {
109
+ key : otherStorageKey ,
110
+ newValue : otherIndex . toString ( ) ,
111
+ } )
112
+ )
113
+ }
46
114
}
47
115
48
116
export function Code ( { children } : ChildrenProps ) {
49
117
const router = useRouter ( )
50
118
const searchParams = useSearchParams ( )
51
- const childs = Children . toArray ( children )
119
+ const childElements = Children . toArray ( children )
52
120
const { project } = useThemeConfig ( )
53
121
54
- const withNextJsPages = childs . some (
122
+ const withNextJsPages = childElements . some (
55
123
// @ts -expect-error: Hacky dynamic child wrangling
56
124
( p ) => p && p . type . name === NextClientCode . name
57
125
)
58
126
59
127
const renderedFrameworks = withNextJsPages ? allFrameworks : baseFrameworks
60
128
61
- const updateFrameworkStorage = ( value : string ) : void => {
129
+ const updateFrameworkInUrl = ( frameworkURI : string ) : void => {
130
+ if ( frameworkURI === "undefined" ) return
131
+
62
132
const params = new URLSearchParams ( searchParams ?. toString ( ) )
63
- params . set ( "framework" , value )
64
- router . push ( `${ router . pathname } ?${ params . toString ( ) } ` )
133
+ params . set ( "framework" , frameworkURI )
134
+
135
+ router . push ( `${ router . pathname } ?${ params . toString ( ) } ` , undefined , {
136
+ scroll : false ,
137
+ } )
65
138
}
66
139
67
140
const handleClickFramework = ( event : MouseEvent < HTMLDivElement > ) => {
68
141
if ( ! ( event . target instanceof HTMLButtonElement ) ) return
69
142
const { textContent } = event . target as unknown as HTMLDivElement
70
- updateFrameworkStorage ( parseParams ( textContent ! ) )
143
+ if ( ! textContent ) return
144
+
145
+ const frameworkURI = encodeURI ( textContent )
146
+ updateFrameworkInUrl ( frameworkURI )
147
+ updateFrameworkStorage ( frameworkURI , renderedFrameworks , withNextJsPages )
148
+
149
+ // Focus and scroll to maintain position when code blocks above are expanded
150
+ const element = event . target as HTMLButtonElement
151
+ const rect = element . getBoundingClientRect ( )
152
+ requestAnimationFrame ( ( ) => {
153
+ element . focus ( )
154
+ window . scrollBy ( 0 , element . getBoundingClientRect ( ) . top - rect . top )
155
+ } )
71
156
}
72
157
73
158
useEffect ( ( ) => {
74
- const length = Object . keys ( renderedFrameworks ) . length
75
- const getFrameworkStorage = window . localStorage . getItem ( AUTHJS_TAB_KEY )
76
- const indexFramework = parseInt ( getFrameworkStorage ?? "0" ) % length
77
- if ( ! getFrameworkStorage ) {
78
- window . localStorage . setItem ( AUTHJS_TAB_KEY , "0" )
79
- }
80
- updateFrameworkStorage (
81
- parseParams ( Object . values ( renderedFrameworks ) [ indexFramework ] )
159
+ const indexFrameworkFromStorage = getIndexFrameworkFromStorage (
160
+ renderedFrameworks ,
161
+ withNextJsPages
82
162
)
83
- } , [ router . pathname , renderedFrameworks ] )
163
+ const indexFrameworkFromUrl = getIndexFrameworkFromUrl (
164
+ router . asPath ,
165
+ renderedFrameworks
166
+ )
167
+
168
+ if ( indexFrameworkFromStorage === null ) {
169
+ updateFrameworkStorage (
170
+ encodeURI ( renderedFrameworks [ indexFrameworkFromUrl ?? 0 ] ) ,
171
+ renderedFrameworks ,
172
+ withNextJsPages
173
+ )
174
+ }
175
+
176
+ if ( ! indexFrameworkFromUrl ) {
177
+ const index = indexFrameworkFromStorage ?? 0
178
+ updateFrameworkInUrl ( encodeURI ( renderedFrameworks [ index ] ) )
179
+ }
180
+ } , [ router . pathname , renderedFrameworks , withNextJsPages ] )
84
181
85
182
return (
86
183
< div
87
184
className = "[&_div[role='tablist']]:!pb-0"
88
185
onClick = { handleClickFramework }
89
186
>
90
187
< Tabs
91
- storageKey = { AUTHJS_TAB_KEY }
188
+ storageKey = { withNextJsPages ? AUTHJS_TAB_KEY_ALL : AUTHJS_TAB_KEY }
92
189
items = { Object . values ( renderedFrameworks ) }
93
190
>
94
191
{ Object . keys ( renderedFrameworks ) . map ( ( f ) => {
95
192
// @ts -expect-error: Hacky dynamic child wrangling
96
- const child = childs . find ( ( c ) => c ?. type ?. name === f )
193
+ const child = childElements . find ( ( c ) => c ?. type ?. name === f )
97
194
98
195
// @ts -expect-error: Hacky dynamic child wrangling
99
196
return Object . keys ( child ?. props ?? { } ) . length ? (
0 commit comments