@@ -16,79 +16,37 @@ export default function Docs(): JSX.Element {
1616 const { showMessage, clearMessages } = useMessageBanner ( )
1717
1818 const [ iframePageNotFound , setIframePageNotFound ] = useState < boolean > ( false )
19- const [ versions , setVersions ] = useState < ProjectDetails [ ] > ( [ ] )
19+ const [ versions , setVersions ] = useState < ProjectDetails [ ] | null > ( )
2020 const [ loadingFailed , setLoadingFailed ] = useState < boolean > ( false )
2121
2222 const project = useRef ( params . project ?? '' )
23+ const version = useRef ( params . version ?? 'latest' )
2324 const page = useRef ( params [ '*' ] ?? '' )
2425 const hash = useRef ( location . hash )
2526
26- const [ version , setVersion ] = useState < string > ( params . version ?? 'latest' )
27- const [ hideUi , setHideUi ] = useState < boolean > ( searchParams . get ( 'hide-ui' ) === '' || searchParams . get ( 'hide-ui' ) === 'true' )
28- const [ iframeUpdateTrigger , setIframeUpdateTrigger ] = useState < number > ( 0 )
27+ const [ hideUi , setHideUi ] = useState < boolean > (
28+ searchParams . get ( 'hide-ui' ) === '' || searchParams . get ( 'hide-ui' ) === 'true'
29+ )
30+ const [ iFrameUpdateTrigger , setIFrameUpdateTrigger ] = useState < number > ( 0 )
2931
3032 // This provides the url for the iframe.
3133 // It is always the same, except when the version changes,
3234 // as this memo will trigger a re-render of the iframe, which
3335 // is not needed when only the page or hash changes, because
3436 // the iframe keeps track of that itself.
3537 const iFrameSrc = useMemo ( ( ) => {
38+ if ( versions == null ) {
39+ return ''
40+ }
41+
3642 return ProjectRepository . getProjectDocsURL (
3743 project . current ,
38- version ,
44+ version . current ,
3945 page . current ,
4046 hash . current
4147 )
4248 // eslint-disable-next-line react-hooks/exhaustive-deps
43- } , [ version , iframeUpdateTrigger ] )
44-
45- useEffect ( ( ) => {
46- if ( project . current === '' ) {
47- return
48- }
49-
50- void ( async ( ) : Promise < void > => {
51- try {
52- let allVersions = await ProjectRepository . getVersions ( project . current )
53- if ( allVersions . length === 0 ) {
54- setLoadingFailed ( true )
55- return
56- }
57-
58- allVersions = allVersions . sort ( ( a , b ) =>
59- ProjectRepository . compareVersions ( a , b )
60- )
61- setVersions ( allVersions )
62-
63- const latestVersion =
64- ProjectRepository . getLatestVersion ( allVersions ) . name
65- if ( version === 'latest' ) {
66- if ( latestVersion === 'latest' ) {
67- return
68- }
69- setVersion ( latestVersion )
70- return
71- }
72-
73- // custom version -> check if it exists
74- // if it does. do nothing, as it should be set already
75- const versionsAndTags = allVersions
76- . map ( ( v ) => [ v . name , ...v . tags ] )
77- . flat ( )
78- if ( versionsAndTags . includes ( version ) ) {
79- return
80- }
81-
82- // version does not exist -> fail
83- setLoadingFailed ( true )
84- console . error ( `Version '${ version } ' doesn't exist` )
85- } catch ( e ) {
86- console . error ( e )
87- setLoadingFailed ( true )
88- }
89- } ) ( )
90- // eslint-disable-next-line react-hooks/exhaustive-deps
91- } , [ project ] )
49+ } , [ iFrameUpdateTrigger ] )
9250
9351 /** Encodes the url for the current page.
9452 * @example
@@ -105,109 +63,167 @@ export default function Docs(): JSX.Element {
10563 }
10664
10765 const updateUrl = ( newVersion : string , hideUi : boolean ) : void => {
108- const url = getUrl (
66+ const newUrl = getUrl (
10967 project . current ,
11068 newVersion ,
11169 page . current ,
11270 hash . current ,
11371 hideUi
11472 )
115- window . history . pushState ( null , '' , url )
73+
74+ const currentUrl = location . pathname + location . search + location . hash
75+ if ( newUrl !== currentUrl ) {
76+ console . log ( `Updating url from '${ currentUrl } ' to '${ newUrl } '` )
77+ // avoid updating the url when it hasn't changed
78+ // because this results in the user having to click back twice or more.
79+ window . history . pushState ( null , '' , newUrl )
80+ }
11681 }
11782
11883 const updateTitle = ( newTitle : string ) : void => {
11984 document . title = newTitle
12085 }
12186
122- // Keep compatibility with encoded page path
123- useEffect ( ( ) => {
124- updateUrl ( version , hideUi )
125- // eslint-disable-next-line react-hooks/exhaustive-deps
126- } , [ ] )
87+ const triggerIFrameUpdate = ( ) : void => {
88+ setIFrameUpdateTrigger ( ( v ) => v + 1 )
89+ }
12790
128- const iFramePageChanged = ( urlPage : string , urlHash : string , title ?: string ) : void => {
91+ const onIFramePageChanged = (
92+ urlPage : string ,
93+ urlHash : string ,
94+ title ?: string
95+ ) : void => {
12996 if ( title != null && title !== document . title ) {
13097 updateTitle ( title )
13198 }
99+
132100 if ( urlPage === page . current ) {
133101 return
134102 }
103+
135104 page . current = urlPage
136105 hash . current = urlHash
137- updateUrl ( version , hideUi )
106+ updateUrl ( version . current , hideUi )
138107 }
139108
140- const iFrameHashChanged = ( newHash : string ) : void => {
109+ const onIFrameHashChanged = ( newHash : string ) : void => {
141110 if ( newHash === hash . current ) {
142111 return
143112 }
144113 hash . current = newHash
145- updateUrl ( version , hideUi )
114+ updateUrl ( version . current , hideUi )
146115 }
147116
148- const iFrameNotFound = ( ) : void => {
117+ const onIFrameNotFound = ( ) : void => {
149118 setIframePageNotFound ( true )
150119 }
151120
152- const iFrameFaviconChanged = ( faviconUrl : string | null ) : void => {
153- const favicon = document . querySelector ( 'link[rel="icon"]' ) as HTMLLinkElement | null
154- if ( favicon == null || faviconUrl == null ) {
155- return
121+ const onIFrameFaviconChanged = ( faviconUrl : string | null ) : void => {
122+ const favicon = document . querySelector (
123+ 'link[rel="icon"]'
124+ ) as HTMLLinkElement | null
125+ if ( favicon != null && faviconUrl != null ) {
126+ favicon . href = faviconUrl
156127 }
157- favicon . href = faviconUrl
158128 }
159129
160130 const onVersionChanged = ( newVersion : string ) : void => {
161- if ( newVersion === version ) {
131+ if ( newVersion === version . current ) {
162132 return
163133 }
164- setVersion ( newVersion )
134+
135+ version . current = newVersion
136+ triggerIFrameUpdate ( )
165137 updateUrl ( newVersion , hideUi )
166138 }
167139
168140 useEffect ( ( ) => {
141+ console . log ( 'location hook' )
142+ // hook to parse url parameters into the state on first load
169143 const urlProject = params . project ?? ''
170144 const urlVersion = params . version ?? 'latest'
171145 const urlPage = params [ '*' ] ?? ''
172146 const urlHash = location . hash
173- const urlHideUi = searchParams . get ( 'hide-ui' ) === '' || searchParams . get ( 'hide-ui' ) === 'true'
147+ const urlHideUi =
148+ searchParams . get ( 'hide-ui' ) === '' ||
149+ searchParams . get ( 'hide-ui' ) === 'true'
174150
175- // update the state to the url params on first loadon
176151 if ( urlProject !== project . current ) {
177152 setVersions ( [ ] )
178153 project . current = urlProject
154+ triggerIFrameUpdate ( )
179155 }
180-
181- if ( urlVersion !== version ) {
182- setVersion ( urlVersion )
156+ if ( urlVersion !== version . current && urlVersion !== 'latest' ) {
157+ version . current = urlVersion
158+ triggerIFrameUpdate ( )
183159 }
184-
185- if ( urlHideUi !== hideUi ) {
186- setHideUi ( urlHideUi )
187- }
188-
189160 if ( urlPage !== page . current ) {
190161 page . current = urlPage
191- setIframeUpdateTrigger ( ( v ) => v + 1 )
162+ triggerIFrameUpdate ( )
192163 }
193164 if ( urlHash !== hash . current ) {
194165 hash . current = urlHash
195- setIframeUpdateTrigger ( ( v ) => v + 1 )
166+ triggerIFrameUpdate ( )
167+ }
168+ if ( urlHideUi !== hideUi ) {
169+ setHideUi ( urlHideUi )
196170 }
197171
198172 setIframePageNotFound ( false )
199173 // eslint-disable-next-line react-hooks/exhaustive-deps
200174 } , [ location ] )
201175
176+ useEffect ( ( ) => {
177+ void ( async ( ) : Promise < void > => {
178+ try {
179+ const allVersions = await ProjectRepository . getVersions ( project . current )
180+ if ( allVersions . length === 0 ) {
181+ setLoadingFailed ( true )
182+ return
183+ }
184+
185+ const sortedVersions = allVersions . sort ( ( a , b ) =>
186+ ProjectRepository . compareVersions ( a , b )
187+ )
188+ setVersions ( sortedVersions )
189+
190+ // custom version -> check if it exists
191+ // if it does, do nothing, as it should be set already
192+ const versionsAndTags = sortedVersions
193+ . map ( ( v ) => [ v . name , ...v . tags ] )
194+ . flat ( )
195+
196+ if ( version . current === 'latest' ) {
197+ version . current =
198+ ProjectRepository . getLatestVersion ( sortedVersions ) . name
199+ }
200+ triggerIFrameUpdate ( )
201+
202+ if (
203+ versionsAndTags . length === 0 ||
204+ ! versionsAndTags . includes ( version . current )
205+ ) {
206+ setLoadingFailed ( true )
207+ console . error ( `Version '${ version } ' doesn't exist` )
208+ }
209+ } catch ( e ) {
210+ console . error ( e )
211+ setLoadingFailed ( true )
212+ }
213+ } ) ( )
214+ } , [ project ] )
215+
202216 useEffect ( ( ) => {
203217 // check every time the version changes whether the version
204218 // is the latest version and if not, show a banner
205- if ( versions . length === 0 ) {
219+ if ( versions == null || loadingFailed ) {
206220 return
207221 }
208222
209223 const latestVersion = ProjectRepository . getLatestVersion ( versions ) . name
210- if ( version === latestVersion ) {
224+ const isLatestVersion =
225+ version . current === latestVersion || version . current === 'latest'
226+ if ( isLatestVersion ) {
211227 clearMessages ( )
212228 return
213229 }
@@ -220,37 +236,43 @@ export default function Docs(): JSX.Element {
220236 // eslint-disable-next-line react-hooks/exhaustive-deps
221237 } , [ version , versions ] )
222238
223- if ( loadingFailed || project . current === '' ) {
239+ // Keep compatibility with encoded page path
240+ useEffect ( ( ) => {
241+ updateUrl ( version . current , hideUi )
242+ // eslint-disable-next-line react-hooks/exhaustive-deps
243+ } , [ ] )
244+
245+ if ( loadingFailed ) {
224246 return < NotFound />
225247 }
226248
249+ if ( versions == null ) {
250+ return < LoadingPage />
251+ }
252+
227253 if ( iframePageNotFound ) {
228254 return (
229255 < IframeNotFound
230256 project = { project . current }
231- version = { version }
257+ version = { version . current }
232258 hideUi = { hideUi }
233259 />
234260 )
235261 }
236262
237- if ( versions . length === 0 ) {
238- return < LoadingPage />
239- }
240-
241263 return (
242264 < >
243265 < IFrame
244266 src = { iFrameSrc }
245- onPageChanged = { iFramePageChanged }
246- onHashChanged = { iFrameHashChanged }
267+ onPageChanged = { onIFramePageChanged }
268+ onHashChanged = { onIFrameHashChanged }
247269 onTitleChanged = { updateTitle }
248- onNotFound = { iFrameNotFound }
249- onFaviconChanged = { iFrameFaviconChanged }
270+ onNotFound = { onIFrameNotFound }
271+ onFaviconChanged = { onIFrameFaviconChanged }
250272 />
251273 { ! hideUi && (
252274 < DocumentControlButtons
253- version = { version }
275+ version = { version . current }
254276 versions = { versions }
255277 onVersionChange = { onVersionChanged }
256278 />
0 commit comments