Skip to content

Commit aeb9492

Browse files
authored
Fixed stay no page functionality when using mike's canonical versioning (#7559)
1 parent 50a15be commit aeb9492

File tree

3 files changed

+150
-13
lines changed

3 files changed

+150
-13
lines changed

Diff for: docs/setup/setting-up-versioning.md

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

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
import { Sitemap } from "../../sitemap"
2+
3+
/** See docstring for `selectedVersionCorrespondingURL` for the meaning of these fields. */
4+
type CorrespondingURLParams = {
5+
selectedVersionSitemap: Sitemap
6+
selectedVersionBaseURL: URL
7+
currentLocation: URL
8+
currentBaseURL: string
9+
}
10+
11+
/**
12+
* Choose a URL to navigate to when the user chooses a version in the version
13+
* selector.
14+
*
15+
* The parameters in `params` are named as follows, in order to make it clearer
16+
* which parameter means what when invoking the function:
17+
*
18+
* - selectedVersionSitemap: Sitemap - as obtained by fetchSitemap from `${selectedVersionBaseURL}/sitemap.xml`
19+
*
20+
* - selectedVersionBaseURL: URL - usually `${currentBaseURL}/../selectedVersion`
21+
*
22+
* - currentLocation: URL - current web browser location
23+
*
24+
* - currentBaseURL: string - as obtained from `config.base`
25+
*
26+
* @param params - arguments with the meanings explained above.
27+
* @returns the URL to navigate to or null if we can't be sure that the
28+
* corresponding page to the current page exists in the selected version
29+
*/
30+
export function selectedVersionCorrespondingURL(
31+
params: CorrespondingURLParams
32+
): URL | undefined {
33+
const {selectedVersionSitemap,
34+
selectedVersionBaseURL,
35+
currentLocation,
36+
currentBaseURL} = params
37+
const current_path = safeURLParse(currentBaseURL)?.pathname
38+
if (current_path === undefined) {
39+
return
40+
}
41+
const currentRelativePath = stripPrefix(currentLocation.pathname, current_path)
42+
if (currentRelativePath === undefined) {
43+
return
44+
}
45+
const sitemapCommonPrefix = shortestCommonPrefix(selectedVersionSitemap.keys())
46+
if (!selectedVersionSitemap.has(sitemapCommonPrefix)) {
47+
// We could also check that `commonSitemapPrefix` ends in the canonical version,
48+
// similarly to https://github.com/squidfunk/mkdocs-material/pull/7227. However,
49+
// I don't believe that Mike/MkDocs ever generate sitemaps where it would matter
50+
return
51+
}
52+
53+
const potentialSitemapURL = safeURLParse(currentRelativePath, sitemapCommonPrefix)
54+
if (!potentialSitemapURL || !selectedVersionSitemap.has(potentialSitemapURL.href)) {
55+
return
56+
}
57+
58+
const result = safeURLParse(currentRelativePath, selectedVersionBaseURL)
59+
if (!result) {
60+
return
61+
}
62+
result.hash = currentLocation.hash
63+
result.search = currentLocation.search
64+
return result
65+
}
66+
67+
/**
68+
* A version of `new URL` that never throws. A polyfill for URL.parse() which is
69+
* not yet ubuquitous.
70+
*
71+
* @param url - passed to `new URL` constructor
72+
* @param base - passed to `new URL` constructor
73+
*
74+
* @returns `new URL(url, base)` or undefined if the URL is invalid.
75+
*/
76+
function safeURLParse(url: string|URL, base?: string|URL): URL | undefined {
77+
try {
78+
return new URL(url, base)
79+
} catch {
80+
return
81+
}
82+
}
83+
84+
// Basic string manipulation
85+
86+
/** Strip a given prefix from a function
87+
*
88+
* @param s - string
89+
* @param prefix - prefix to strip
90+
*
91+
* @returns either the string with the prefix stripped or undefined if the
92+
* string did not begin with the prefix.
93+
*/
94+
export function stripPrefix(s: string, prefix: string): string | undefined {
95+
if (s.startsWith(prefix)) {
96+
return s.slice(prefix.length)
97+
}
98+
return undefined
99+
}
100+
101+
/** Find the length of the longest common prefix of two strings
102+
*
103+
* @param s1 - first string
104+
* @param s2 - second string
105+
*
106+
* @returns - the length of the longest common prefix of the two strings.
107+
*/
108+
function commonPrefixLen(s1: string, s2: string): number {
109+
const max = Math.min(s1.length, s2.length)
110+
let result
111+
for (result = 0; result < max; ++result) {
112+
if (s1[result] !== s2[result]) {
113+
break
114+
}
115+
}
116+
return result
117+
}
118+
119+
/** Find the longest common prefix of any number of strings
120+
*
121+
* @param strs - an iterable of strings
122+
*
123+
* @returns the longest common prefix of all the strings
124+
*/
125+
export function shortestCommonPrefix(strs: Iterable<string>): string {
126+
let result // Undefined if no iterations happened
127+
for (const s of strs) {
128+
if (result === undefined) {
129+
result = s
130+
} else {
131+
result = result.slice(0, commonPrefixLen(result, s))
132+
}
133+
}
134+
return result ?? ""
135+
}

Diff for: src/templates/assets/javascripts/integrations/version/index.ts

+15-12
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@ import {
4848

4949
import { fetchSitemap } from "../sitemap"
5050

51+
import { selectedVersionCorrespondingURL } from "./findurl"
52+
5153
/* ----------------------------------------------------------------------------
5254
* Helper types
5355
* ------------------------------------------------------------------------- */
@@ -122,22 +124,23 @@ export function setupVersionSelector(
122124
return EMPTY
123125
}
124126
ev.preventDefault()
125-
return of(url)
127+
return of(new URL(url))
126128
}
127129
}
128130
return EMPTY
129131
}),
130-
switchMap(url => {
131-
return fetchSitemap(new URL(url))
132-
.pipe(
133-
map(sitemap => {
134-
const location = getLocation()
135-
const path = location.href.replace(config.base, url)
136-
return sitemap.has(path.split("#")[0])
137-
? new URL(path)
138-
: new URL(url)
139-
})
140-
)
132+
switchMap(selectedVersionBaseURL => {
133+
return fetchSitemap(selectedVersionBaseURL).pipe(
134+
map(
135+
sitemap =>
136+
selectedVersionCorrespondingURL({
137+
selectedVersionSitemap: sitemap,
138+
selectedVersionBaseURL,
139+
currentLocation: getLocation(),
140+
currentBaseURL: config.base
141+
}) ?? selectedVersionBaseURL,
142+
),
143+
)
141144
})
142145
)
143146
)

0 commit comments

Comments
 (0)