Skip to content

Commit 0cb9c37

Browse files
tbuerli-komaxfliiiix
authored andcommitted
fix(web): Fix browser back navigation
Back navigation trough pages showing the "lastest" version, when the version is for example called v1.0.1, by splitting version and displayVersion in the code
1 parent 25b017f commit 0cb9c37

4 files changed

Lines changed: 79 additions & 223 deletions

File tree

web/src/components/DocumentControlButtons.tsx

Lines changed: 3 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ interface Props {
1919
version: string
2020
versions: ProjectDetails[]
2121
onVersionChange: (version: string) => void
22+
getShareUrl: (options: { useLatest: boolean, hideUi: boolean }) => string
2223
}
2324

2425
export default function DocumentControlButtons(props: Props): React.JSX.Element {
@@ -31,25 +32,6 @@ export default function DocumentControlButtons(props: Props): React.JSX.Element
3132
// Cannot copy when page is served over HTTP
3233
const canCopy = navigator.clipboard !== undefined
3334

34-
const getShareUrl = (): string => {
35-
// adapt the current URL so we can leave Docs.tsx's state as refs
36-
// (which means if the page was passed down as a prop it wouldn't update correctly)
37-
38-
let url = window.location.href
39-
40-
if (shareModalUseLatest) {
41-
url = url.replace(props.version, 'latest')
42-
}
43-
44-
if (shareModalHideUi) {
45-
const urlObject = new URL(url)
46-
urlObject.search = 'hide-ui'
47-
url = urlObject.toString()
48-
}
49-
50-
return url
51-
}
52-
5335
return (
5436
<div className={styles.controls}>
5537
<Tooltip title="Docs Overview" placement="top" arrow>
@@ -111,14 +93,14 @@ export default function DocumentControlButtons(props: Props): React.JSX.Element
11193
>
11294
<div className={styles['share-modal']}>
11395
<div className={styles['share-modal-link-container']}>
114-
<p className={styles['share-modal-link']}>{getShareUrl()}</p>
96+
<p className={styles['share-modal-link']}>{props.getShareUrl({ useLatest: shareModalUseLatest, hideUi: shareModalHideUi })}</p>
11597
{canCopy && (
11698
<div className={styles['share-modal-copy-container']}>
11799
<button
118100
className={styles['share-modal-copy']}
119101
onClick={() => {
120102
void (async () => {
121-
await navigator.clipboard.writeText(getShareUrl())
103+
await navigator.clipboard.writeText(props.getShareUrl({ useLatest: shareModalUseLatest, hideUi: shareModalHideUi }))
122104
})()
123105
}}
124106
>

web/src/pages/Docs.tsx

Lines changed: 76 additions & 135 deletions
Original file line numberDiff line numberDiff line change
@@ -7,22 +7,22 @@ import DocumentControlButtons from '../components/DocumentControlButtons'
77
import IFrame from '../components/IFrame'
88
import { useLocation, useParams, useSearchParams } from 'react-router-dom'
99
import { useMessageBanner } from '../data-providers/MessageBannerProvider'
10-
import IframeNotFound from './IframePageNotFound'
1110

1211
export default function Docs(): JSX.Element {
1312
const params = useParams()
1413
const [searchParams] = useSearchParams()
1514
const location = useLocation()
1615
const { showMessage, clearMessages } = useMessageBanner()
1716

18-
const [iframePageNotFound, setIframePageNotFound] = useState<boolean>(false)
1917
const [versions, setVersions] = useState<ProjectDetails[]>([])
20-
const [loadingFailed, setLoadingFailed] = useState<boolean>(false)
18+
const [displayVersion, setDisplayVersion] = useState<ProjectDetails | null>(null)
19+
const [projectLoading, setProjectLoading] = useState<boolean>(true)
20+
const [notFound, setNotFound] = useState<boolean>(false)
2121

22-
const project = useRef(params.project ?? '')
2322
const page = useRef(params['*'] ?? '')
2423
const hash = useRef(location.hash)
2524

25+
const [project, setProject] = useState<string>(params.project ?? '')
2626
const [version, setVersion] = useState<string>(params.version ?? 'latest')
2727
const [hideUi, setHideUi] = useState<boolean>(searchParams.get('hide-ui') === '' || searchParams.get('hide-ui') === 'true')
2828
const [iframeUpdateTrigger, setIframeUpdateTrigger] = useState<number>(0)
@@ -33,97 +33,83 @@ export default function Docs(): JSX.Element {
3333
// is not needed when only the page or hash changes, because
3434
// the iframe keeps track of that itself.
3535
const iFrameSrc = useMemo(() => {
36+
if (!displayVersion) {
37+
return ''
38+
}
3639
return ProjectRepository.getProjectDocsURL(
37-
project.current,
38-
version,
40+
project,
41+
displayVersion.name,
3942
page.current,
4043
hash.current
4144
)
4245
// eslint-disable-next-line react-hooks/exhaustive-deps
43-
}, [version, iframeUpdateTrigger])
46+
}, [project, displayVersion, iframeUpdateTrigger])
4447

4548
useEffect(() => {
46-
if (project.current === '') {
47-
return
48-
}
49-
50-
void (async (): Promise<void> => {
49+
setProjectLoading(true)
50+
const loadProject = async () => {
5151
try {
52-
let allVersions = await ProjectRepository.getVersions(project.current)
53-
if (allVersions.length === 0) {
54-
setLoadingFailed(true)
55-
return
56-
}
57-
52+
let allVersions = await ProjectRepository.getVersions(project)
5853
allVersions = allVersions.sort((a, b) =>
5954
ProjectRepository.compareVersions(a, b)
6055
)
6156
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`)
8557
} catch (e) {
8658
console.error(e)
87-
setLoadingFailed(true)
8859
}
89-
})()
90-
// eslint-disable-next-line react-hooks/exhaustive-deps
91-
}, [project])
60+
}
61+
loadProject().finally(() => {
62+
setProjectLoading(false)
63+
})
64+
}, [project]);
9265

93-
/** Encodes the url for the current page.
94-
* @example
95-
* getUrl('project', 'version', 'path/to/page.html', '#hash', false) -> '/project/version/path/to/page.html#hash'
96-
*/
97-
const getUrl = (
98-
project: string,
99-
version: string,
100-
page: string,
101-
hash: string,
102-
hideUi: boolean
103-
): string => {
104-
return `/${project}/${version}/${encodeURI(page)}${hash}${hideUi ? '?hide-ui' : ''}`
66+
const buildBrowserUrl = (project: string, version: string, page: string, hash: string, hideUi: boolean): string => {
67+
return `/${project}/${version}/${page}${hideUi ? '?hide-ui' : ''}${hash}`
10568
}
10669

107-
const updateUrl = (newVersion: string, hideUi: boolean): void => {
108-
const url = getUrl(
109-
project.current,
110-
newVersion,
111-
page.current,
112-
hash.current,
113-
hideUi
114-
)
115-
window.history.pushState(null, '', url)
70+
const getShareUrl = (options: { useLatest: boolean, hideUi: boolean }): string => {
71+
return buildBrowserUrl(project, options.useLatest ? 'latest' : displayVersion?.name ?? 'latest', page.current, hash.current, options.hideUi)
11672
}
11773

118-
const updateTitle = (newTitle: string): void => {
119-
document.title = newTitle
74+
const updateUrl = (newProject: string, newVersion: string, hideUi: boolean): void => {
75+
window.history.pushState(null, '', buildBrowserUrl(newProject, newVersion, page.current, hash.current, hideUi))
12076
}
12177

122-
// Keep compatibility with encoded page path
12378
useEffect(() => {
124-
updateUrl(version, hideUi)
125-
// eslint-disable-next-line react-hooks/exhaustive-deps
126-
}, [])
79+
if (versions.length === 0) {
80+
return
81+
}
82+
83+
if (version === 'latest') {
84+
const latestVersion = ProjectRepository.getLatestVersion(versions)
85+
setDisplayVersion(latestVersion)
86+
} else {
87+
const matchingVersion = versions.find((v) => v.name === version || v.tags.includes(version))
88+
if (matchingVersion) {
89+
setDisplayVersion(matchingVersion)
90+
} else {
91+
setNotFound(true)
92+
console.error(`Version '${version}' doesn't exist`)
93+
}
94+
}
95+
}, [versions, version])
96+
97+
useEffect(() => {
98+
const latestVersion = ProjectRepository.getLatestVersion(versions)
99+
if (displayVersion === latestVersion) {
100+
clearMessages()
101+
} else {
102+
showMessage({
103+
content: 'You are viewing an outdated version of the documentation.',
104+
type: 'warning',
105+
showMs: null
106+
})
107+
}
108+
}, [displayVersion, versions, showMessage, clearMessages])
109+
110+
const updateTitle = (newTitle: string): void => {
111+
document.title = newTitle
112+
}
127113

128114
const iFramePageChanged = (urlPage: string, urlHash: string, title?: string): void => {
129115
if (title != null && title !== document.title) {
@@ -134,19 +120,19 @@ export default function Docs(): JSX.Element {
134120
}
135121
page.current = urlPage
136122
hash.current = urlHash
137-
updateUrl(version, hideUi)
123+
updateUrl(project, version, hideUi)
138124
}
139125

140126
const iFrameHashChanged = (newHash: string): void => {
141127
if (newHash === hash.current) {
142128
return
143129
}
144130
hash.current = newHash
145-
updateUrl(version, hideUi)
131+
updateUrl(project, version, hideUi)
146132
}
147133

148134
const iFrameNotFound = (): void => {
149-
setIframePageNotFound(true)
135+
setNotFound(true)
150136
}
151137

152138
const iFrameFaviconChanged = (faviconUrl: string | null): void => {
@@ -157,34 +143,18 @@ export default function Docs(): JSX.Element {
157143
favicon.href = faviconUrl
158144
}
159145

160-
const onVersionChanged = (newVersion: string): void => {
161-
if (newVersion === version) {
162-
return
163-
}
164-
setVersion(newVersion)
165-
updateUrl(newVersion, hideUi)
166-
}
167-
168146
useEffect(() => {
169147
const urlProject = params.project ?? ''
170148
const urlVersion = params.version ?? 'latest'
171149
const urlPage = params['*'] ?? ''
172150
const urlHash = location.hash
173151
const urlHideUi = searchParams.get('hide-ui') === '' || searchParams.get('hide-ui') === 'true'
174152

175-
// update the state to the url params on first loadon
176-
if (urlProject !== project.current) {
177-
setVersions([])
178-
project.current = urlProject
179-
}
180-
181-
if (urlVersion !== version) {
182-
setVersion(urlVersion)
183-
}
184-
185-
if (urlHideUi !== hideUi) {
186-
setHideUi(urlHideUi)
187-
}
153+
// update the state to the url params on first load
154+
setNotFound(false)
155+
setProject(urlProject)
156+
setVersion(urlVersion)
157+
setHideUi(urlHideUi)
188158

189159
if (urlPage !== page.current) {
190160
page.current = urlPage
@@ -194,48 +164,15 @@ export default function Docs(): JSX.Element {
194164
hash.current = urlHash
195165
setIframeUpdateTrigger((v) => v + 1)
196166
}
197-
198-
setIframePageNotFound(false)
199167
// eslint-disable-next-line react-hooks/exhaustive-deps
200168
}, [location])
201169

202-
useEffect(() => {
203-
// check every time the version changes whether the version
204-
// is the latest version and if not, show a banner
205-
if (versions.length === 0) {
206-
return
207-
}
208-
209-
const latestVersion = ProjectRepository.getLatestVersion(versions).name
210-
if (version === latestVersion) {
211-
clearMessages()
212-
return
213-
}
214-
215-
showMessage({
216-
content: 'You are viewing an outdated version of the documentation.',
217-
type: 'warning',
218-
showMs: null
219-
})
220-
// eslint-disable-next-line react-hooks/exhaustive-deps
221-
}, [version, versions])
222-
223-
if (loadingFailed || project.current === '') {
224-
return <NotFound />
225-
}
226-
227-
if (iframePageNotFound) {
228-
return (
229-
<IframeNotFound
230-
project={project.current}
231-
version={version}
232-
hideUi={hideUi}
233-
/>
234-
)
170+
if (projectLoading) {
171+
return <LoadingPage />
235172
}
236173

237-
if (versions.length === 0) {
238-
return <LoadingPage />
174+
if (displayVersion == null || notFound) {
175+
return <NotFound />
239176
}
240177

241178
return (
@@ -250,9 +187,13 @@ export default function Docs(): JSX.Element {
250187
/>
251188
{!hideUi && (
252189
<DocumentControlButtons
253-
version={version}
190+
version={displayVersion.name}
254191
versions={versions}
255-
onVersionChange={onVersionChanged}
192+
onVersionChange={(newVersion) => {
193+
updateUrl(project, newVersion, hideUi)
194+
setVersion(newVersion)
195+
}}
196+
getShareUrl={getShareUrl}
256197
/>
257198
)}
258199
</>

web/src/pages/IframePageNotFound.tsx

Lines changed: 0 additions & 33 deletions
This file was deleted.

0 commit comments

Comments
 (0)