Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixes to "staying on the same page functionality" (version with tests removed) #7559

Merged
merged 4 commits into from
Sep 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion docs/setup/setting-up-versioning.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
135 changes: 135 additions & 0 deletions src/templates/assets/javascripts/integrations/version/findurl/index.ts
Original file line number Diff line number Diff line change
@@ -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 ?? ""
}
27 changes: 15 additions & 12 deletions src/templates/assets/javascripts/integrations/version/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ import {

import { fetchSitemap } from "../sitemap"

import { selectedVersionCorrespondingURL } from "./findurl"

/* ----------------------------------------------------------------------------
* Helper types
* ------------------------------------------------------------------------- */
Expand Down Expand Up @@ -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,
),
)
})
)
)
Expand Down