@@ -9,6 +9,7 @@ interface ChildrenProps {
99}
1010
1111const AUTHJS_TAB_KEY = "authjs.codeTab.framework"
12+ const AUTHJS_TAB_KEY_ALL = "authjs.codeTab.framework.all"
1213
1314Code . Next = NextCode
1415Code . NextClient = NextClientCode
@@ -34,66 +35,162 @@ const allFrameworks = {
3435 [ ExpressCode . name ] : "Express" ,
3536}
3637
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+ }
46114}
47115
48116export function Code ( { children } : ChildrenProps ) {
49117 const router = useRouter ( )
50118 const searchParams = useSearchParams ( )
51- const childs = Children . toArray ( children )
119+ const childElements = Children . toArray ( children )
52120 const { project } = useThemeConfig ( )
53121
54- const withNextJsPages = childs . some (
122+ const withNextJsPages = childElements . some (
55123 // @ts -expect-error: Hacky dynamic child wrangling
56124 ( p ) => p && p . type . name === NextClientCode . name
57125 )
58126
59127 const renderedFrameworks = withNextJsPages ? allFrameworks : baseFrameworks
60128
61- const updateFrameworkStorage = ( value : string ) : void => {
129+ const updateFrameworkInUrl = ( frameworkURI : string ) : void => {
130+ if ( frameworkURI === "undefined" ) return
131+
62132 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+ } )
65138 }
66139
67140 const handleClickFramework = ( event : MouseEvent < HTMLDivElement > ) => {
68141 if ( ! ( event . target instanceof HTMLButtonElement ) ) return
69142 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+ } )
71156 }
72157
73158 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
82162 )
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 ] )
84181
85182 return (
86183 < div
87184 className = "[&_div[role='tablist']]:!pb-0"
88185 onClick = { handleClickFramework }
89186 >
90187 < Tabs
91- storageKey = { AUTHJS_TAB_KEY }
188+ storageKey = { withNextJsPages ? AUTHJS_TAB_KEY_ALL : AUTHJS_TAB_KEY }
92189 items = { Object . values ( renderedFrameworks ) }
93190 >
94191 { Object . keys ( renderedFrameworks ) . map ( ( f ) => {
95192 // @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 )
97194
98195 // @ts -expect-error: Hacky dynamic child wrangling
99196 return Object . keys ( child ?. props ?? { } ) . length ? (
0 commit comments