1
0
mirror of https://github.com/squidfunk/mkdocs-material.git synced 2024-11-23 23:21:00 +01:00

Fixed stay no page functionality when using mike's canonical versioning (#7559)

This commit is contained in:
Ilya Grigoriev 2024-09-27 00:53:14 -07:00 committed by GitHub
parent 50a15becc8
commit aeb9492c08
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 150 additions and 13 deletions

View File

@ -64,7 +64,6 @@ MkDocs implements this behavior by default, but there are a few caveats:
- the [`site_url`][mkdocs.site_url] must be set correctly in `mkdocs.yml`.
See the ["Publishing a new version"](#publishing-a-new-version) section for
an example.
- you must be viewing the site at that URL (and not locally, for example).
- the redirect happens via JavaScript and there is no way to know which page you
will be redirected to ahead of time.

View File

@ -0,0 +1,135 @@
import { Sitemap } from "../../sitemap"
/** See docstring for `selectedVersionCorrespondingURL` for the meaning of these fields. */
type CorrespondingURLParams = {
selectedVersionSitemap: Sitemap
selectedVersionBaseURL: URL
currentLocation: URL
currentBaseURL: string
}
/**
* Choose a URL to navigate to when the user chooses a version in the version
* selector.
*
* The parameters in `params` are named as follows, in order to make it clearer
* which parameter means what when invoking the function:
*
* - selectedVersionSitemap: Sitemap - as obtained by fetchSitemap from `${selectedVersionBaseURL}/sitemap.xml`
*
* - selectedVersionBaseURL: URL - usually `${currentBaseURL}/../selectedVersion`
*
* - currentLocation: URL - current web browser location
*
* - currentBaseURL: string - as obtained from `config.base`
*
* @param params - arguments with the meanings explained above.
* @returns the URL to navigate to or null if we can't be sure that the
* corresponding page to the current page exists in the selected version
*/
export function selectedVersionCorrespondingURL(
params: CorrespondingURLParams
): URL | undefined {
const {selectedVersionSitemap,
selectedVersionBaseURL,
currentLocation,
currentBaseURL} = params
const current_path = safeURLParse(currentBaseURL)?.pathname
if (current_path === undefined) {
return
}
const currentRelativePath = stripPrefix(currentLocation.pathname, current_path)
if (currentRelativePath === undefined) {
return
}
const sitemapCommonPrefix = shortestCommonPrefix(selectedVersionSitemap.keys())
if (!selectedVersionSitemap.has(sitemapCommonPrefix)) {
// We could also check that `commonSitemapPrefix` ends in the canonical version,
// similarly to https://github.com/squidfunk/mkdocs-material/pull/7227. However,
// I don't believe that Mike/MkDocs ever generate sitemaps where it would matter
return
}
const potentialSitemapURL = safeURLParse(currentRelativePath, sitemapCommonPrefix)
if (!potentialSitemapURL || !selectedVersionSitemap.has(potentialSitemapURL.href)) {
return
}
const result = safeURLParse(currentRelativePath, selectedVersionBaseURL)
if (!result) {
return
}
result.hash = currentLocation.hash
result.search = currentLocation.search
return result
}
/**
* A version of `new URL` that never throws. A polyfill for URL.parse() which is
* not yet ubuquitous.
*
* @param url - passed to `new URL` constructor
* @param base - passed to `new URL` constructor
*
* @returns `new URL(url, base)` or undefined if the URL is invalid.
*/
function safeURLParse(url: string|URL, base?: string|URL): URL | undefined {
try {
return new URL(url, base)
} catch {
return
}
}
// Basic string manipulation
/** Strip a given prefix from a function
*
* @param s - string
* @param prefix - prefix to strip
*
* @returns either the string with the prefix stripped or undefined if the
* string did not begin with the prefix.
*/
export function stripPrefix(s: string, prefix: string): string | undefined {
if (s.startsWith(prefix)) {
return s.slice(prefix.length)
}
return undefined
}
/** Find the length of the longest common prefix of two strings
*
* @param s1 - first string
* @param s2 - second string
*
* @returns - the length of the longest common prefix of the two strings.
*/
function commonPrefixLen(s1: string, s2: string): number {
const max = Math.min(s1.length, s2.length)
let result
for (result = 0; result < max; ++result) {
if (s1[result] !== s2[result]) {
break
}
}
return result
}
/** Find the longest common prefix of any number of strings
*
* @param strs - an iterable of strings
*
* @returns the longest common prefix of all the strings
*/
export function shortestCommonPrefix(strs: Iterable<string>): string {
let result // Undefined if no iterations happened
for (const s of strs) {
if (result === undefined) {
result = s
} else {
result = result.slice(0, commonPrefixLen(result, s))
}
}
return result ?? ""
}

View File

@ -48,6 +48,8 @@ import {
import { fetchSitemap } from "../sitemap"
import { selectedVersionCorrespondingURL } from "./findurl"
/* ----------------------------------------------------------------------------
* Helper types
* ------------------------------------------------------------------------- */
@ -122,22 +124,23 @@ export function setupVersionSelector(
return EMPTY
}
ev.preventDefault()
return of(url)
return of(new URL(url))
}
}
return EMPTY
}),
switchMap(url => {
return fetchSitemap(new URL(url))
.pipe(
map(sitemap => {
const location = getLocation()
const path = location.href.replace(config.base, url)
return sitemap.has(path.split("#")[0])
? new URL(path)
: new URL(url)
})
)
switchMap(selectedVersionBaseURL => {
return fetchSitemap(selectedVersionBaseURL).pipe(
map(
sitemap =>
selectedVersionCorrespondingURL({
selectedVersionSitemap: sitemap,
selectedVersionBaseURL,
currentLocation: getLocation(),
currentBaseURL: config.base
}) ?? selectedVersionBaseURL,
),
)
})
)
)