Skip to content

Commit

Permalink
Improvement: Make deep links work
Browse files Browse the repository at this point in the history
fixes: #479
  • Loading branch information
reglim committed Apr 5, 2023
1 parent ed9362b commit d83ff65
Show file tree
Hide file tree
Showing 2 changed files with 48 additions and 21 deletions.
64 changes: 45 additions & 19 deletions web/src/pages/Docs.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
/* eslint-disable @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call */
/*
the iFrameRef is not really compatiple with ts,
and we need to use some of it's members, which is unsafe
*/

import React, { useEffect, useRef, useState } from 'react'
import { useParams, useSearchParams } from 'react-router-dom'
import { useLocation, useParams, useSearchParams } from 'react-router-dom'
import DocumentControlButtons from '../components/DocumentControlButtons'
import ProjectDetails from '../models/ProjectDetails'
import ProjectRepository from '../repositories/ProjectRepository'
Expand All @@ -20,28 +19,38 @@ export default function Docs (): JSX.Element {
const projectParam = useParams().project ?? ''
const versionParam = useParams().version ?? 'latest'
const pageParam = useParams().page ?? 'index.html'
const hashParam = useLocation().hash ?? ''
const hideUiParam = useSearchParams()[0].get('hide-ui') === 'true'

const [project, setProject] = useState<string>('')
const [version, setVersion] = useState<string>('')
const [page, setPage] = useState<string>('')
const [hash, setHash] = useState<string>('')
const [hideUi, setHideUi] = useState<boolean>(false)

const [versions, setVersions] = useState<ProjectDetails[]>([])
const [loadingFailed, setLoadingFailed] = useState<boolean>(false)

const iFrameRef = useRef(null)
const iFrameRef = useRef<HTMLIFrameElement>(null)

document.title = `${project} | docat`

if (projectParam === '') {
setLoadingFailed(true)
}

const updateURL = (newProject: string, newVersion: string, newPage: string, newHideUi: boolean): void => {
const url = `#/${newProject}/${newVersion}/${newPage}${newHideUi ? '?hide-ui=true' : ''}`
const updateURL = (newProject: string, newVersion: string, newPage: string, newHash: string, newHideUi: boolean): void => {
let url = `#/${newProject}/${newVersion}/${newPage}`

if (project === newProject && version === newVersion && page === newPage && hideUi === newHideUi) {
if (newHash.length > 0) {
url += newHash
}

if (newHideUi) {
url += '?hide-ui=true'
}

if (project === newProject && version === newVersion && page === newPage && hash === newHash && hideUi === newHideUi) {
// no change
return
}
Expand All @@ -52,6 +61,7 @@ export default function Docs (): JSX.Element {
setProject(newProject)
setVersion(newVersion)
setPage(newPage)
setHash(newHash)
setHideUi(newHideUi)

if (oldVersion === 'latest' && newVersion !== 'latest') {
Expand All @@ -69,20 +79,37 @@ export default function Docs (): JSX.Element {
window.history.pushState(null, '', url)
}

const onIFrameLocationChanged = (url: string): void => {
const onIFrameLocationChanged = (url?: string): void => {
if (url == null) {
return
}

url = url.split('/doc/')[1]
if (url.length === 0) {
// should never happen
if (url == null) {
console.error('IFrame URL did not contain "/doc/"')
return
}

// make all external links in iframe open in new tab
// @ts-expect-error - ts does not find the document on the iframe
iFrameRef.current.contentDocument
.querySelectorAll('a')
.forEach((a: HTMLAnchorElement) => {
if (!a.href.startsWith(window.location.origin)) {
a.setAttribute('target', '_blank')
}
})

const parts = url.split('/')
const urlProject = parts[0]
const urlVersion = parts[1]
const urlPage = parts.slice(2).join('/')
const urlPageAndHash = parts.slice(2).join('/')
const hashIndex = urlPageAndHash.includes('#') ? urlPageAndHash.indexOf('#') : urlPageAndHash.length
const urlPage = urlPageAndHash.slice(0, hashIndex)
const urlHash = urlPageAndHash.slice(hashIndex)

if (urlProject !== project || urlVersion !== version || urlPage !== page) {
updateURL(urlProject, urlVersion, urlPage, hideUi)
if (urlProject !== project || urlVersion !== version || urlPage !== page || urlHash !== hash) {
updateURL(urlProject, urlVersion, urlPage, urlHash, hideUi)
}
}

Expand Down Expand Up @@ -118,7 +145,7 @@ export default function Docs (): JSX.Element {
versionToUse = version
}

updateURL(project, versionToUse, page, hideUi)
updateURL(project, versionToUse, page, hash, hideUi)
setVersions(allVersions)
setLoadingFailed(false)
} catch (e) {
Expand All @@ -129,13 +156,12 @@ export default function Docs (): JSX.Element {
}, [project])

useEffect(() => {
// set props equal to url params and update the url with the default values if empty
setProject(p => {
if (p !== '') {
return p
}

updateURL(projectParam, versionParam, pageParam, hideUiParam)
updateURL(projectParam, versionParam, pageParam, hashParam, hideUiParam)
return projectParam
})
}, [])
Expand All @@ -153,21 +179,21 @@ export default function Docs (): JSX.Element {
<iframe
ref={iFrameRef}
key={uniqueId()}
src={ProjectRepository.getProjectDocsURL(project, version, page)}
src={ProjectRepository.getProjectDocsURL(project, version, page, hash)}
title="docs"
className={styles['docs-iframe']}
onLoad={() => {
// @ts-expect-error ts can't find contentWindow
onIFrameLocationChanged(iFrameRef.current?.contentWindow.location.href as string)
onIFrameLocationChanged(iFrameRef.current?.contentWindow.location.href)
}}
/>

{!hideUi && (
<DocumentControlButtons
version={version}
versions={versions}
onVersionChange={(v) => updateURL(project, v, page, hideUi)}
onHideUi={() => updateURL(project, version, page, true)}
onVersionChange={(v) => updateURL(project, v, page, hash, hideUi)}
onHideUi={() => updateURL(project, version, page, hash, true)}
/>
)}
</>
Expand Down
5 changes: 3 additions & 2 deletions web/src/repositories/ProjectRepository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,9 +128,10 @@ function getProjectLogoURL (projectName: string): string {
* @param {string} projectName Name of the project
* @param {string} version Version name
* @param {string?} docsPath Path to the documentation page
* @param {string?} hash Hash part of the url (html id)
*/
function getProjectDocsURL (projectName: string, version: string, docsPath?: string): string {
return `/${RESOURCE}/${projectName}/${version}/${docsPath ?? ''}`
function getProjectDocsURL (projectName: string, version: string, docsPath?: string, hash?: string): string {
return `/${RESOURCE}/${projectName}/${version}/${docsPath ?? ''}${hash ?? ''}`
}

/**
Expand Down

0 comments on commit d83ff65

Please sign in to comment.