Skip to content

Commit 1e83c51

Browse files
committed
Fixes to "staying on the same page functionality"
The new implementation should be very flexible. Fixes #7226 Additionally, this allows using the version switcher even if the website is published at a different URL than the `site_url`. In particular, the version switcher should now work locally when serving the site with `mike serve`. The tests should demonstrate correctness of the implementation.
1 parent f98cb87 commit 1e83c51

File tree

3 files changed

+93
-13
lines changed

3 files changed

+93
-13
lines changed

docs/setup/setting-up-versioning.md

Lines changed: 0 additions & 1 deletion
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

src/templates/assets/javascripts/integrations/version/correspondingPage.test.ts

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { strict as assert } from "assert"
22

33
import { Sitemap } from "../sitemap"
44

5-
import { selectedVersionCorrespondingURL } from "./correspondingPage"
5+
import { selectedVersionCorrespondingURL, shortestCommonPrefix, stripPrefix } from "./correspondingPage"
66

77
describe("Version switcher tests", () => {
88
// These examples are obtained by pausing the JS debugger in various situation and
@@ -54,7 +54,7 @@ describe("Version switcher tests", () => {
5454
"https://test.github.io/project/latest/foo/"
5555
]
5656

57-
it("does not (yet, TODO #7226) return a URL when the selected version has the current page", () => {
57+
it("returns a URL when the selected version has the current page", () => {
5858
assert.equal(
5959
selectedVersionCorrespondingURL({
6060
selectedVersionSitemap: sitemapFromURLList(sitemapURLsLatestVersion),
@@ -64,7 +64,7 @@ describe("Version switcher tests", () => {
6464
currentLocation: new URL("https://test.github.io/project/0.2/bar/#heading?param=some"),
6565
currentBaseURL: "https://test.github.io/project/0.2/"
6666
})?.href,
67-
undefined,
67+
"https://test.github.io/project/0.1/bar/#heading?param=some",
6868
)
6969
})
7070
it("returns nothing when the selected version does not have the current page", () => {
@@ -92,15 +92,15 @@ describe("Version switcher tests", () => {
9292
"https://localhost/project/0.1/foo/"
9393
]
9494

95-
it("does not (yet, TODO) return a URL when the selected version has the current page", () => {
95+
it("returns a URL when the selected version has the current page", () => {
9696
assert.equal(
9797
selectedVersionCorrespondingURL({
9898
selectedVersionSitemap: sitemapFromURLList(sitemapURLsLocalhost),
9999
selectedVersionBaseURL: new URL("https://localhost:8000/0.1/"),
100100
currentLocation: new URL("https://localhost:8000/0.2/bar/#heading?param=some"),
101101
currentBaseURL: "https://localhost:8000/0.2/"
102102
})?.href,
103-
undefined,
103+
"https://localhost:8000/0.1/bar/#heading?param=some",
104104
)
105105
})
106106
it("returns nothing when the selected version does not have the current page", () => {
@@ -130,3 +130,16 @@ describe("Version switcher tests", () => {
130130
function sitemapFromURLList(urls: string[]): Sitemap {
131131
return new Map(urls.map(url => [url, [new URL(url)]]))
132132
}
133+
134+
describe("Utility string processing function tests", () => {
135+
it("shortestCommonPrefix", () => {
136+
assert.equal(shortestCommonPrefix([]), "")
137+
assert.equal(shortestCommonPrefix(["abc", "abcd", "abe"]), "ab")
138+
assert.equal(shortestCommonPrefix(["abcef", "abcd", "abc"]), "abc")
139+
assert.equal(shortestCommonPrefix(["", "abc"]), "")
140+
})
141+
it("stripPrefix", () => {
142+
assert.equal(stripPrefix("abc", "ab"), "c")
143+
assert.equal(stripPrefix("abc", "b"), undefined)
144+
})
145+
})

src/templates/assets/javascripts/integrations/version/correspondingPage.ts

Lines changed: 75 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,79 @@ export function selectedVersionCorrespondingURL(
2727
currentLocation,
2828
currentBaseURL}: CorrespondingURLParams
2929
): URL | undefined {
30-
const result = currentLocation.href.replace(
31-
currentBaseURL,
32-
selectedVersionBaseURL.href,
33-
)
34-
return selectedVersionSitemap.has(result.split("#")[0])
35-
? new URL(result)
36-
: undefined
30+
const current_path = (new URL(currentBaseURL)).pathname
31+
const currentRelativePath = stripPrefix(currentLocation.pathname, current_path)
32+
if (currentRelativePath === undefined) {
33+
return
34+
}
35+
const sitemapCommonPrefix = shortestCommonPrefix(selectedVersionSitemap.keys())
36+
if (!selectedVersionSitemap.has(sitemapCommonPrefix)) {
37+
// We could also check that `commonSitemapPrefix` ends in the canonical version,
38+
// similarly to https://github.com/squidfunk/mkdocs-material/pull/7227. However,
39+
// I don't believe that Mike/MkDocs ever generate sitemaps where it would matter
40+
return
41+
}
42+
43+
const potentialSitemapURL = new URL(currentRelativePath, sitemapCommonPrefix)
44+
if (!selectedVersionSitemap.has(potentialSitemapURL.href)) {
45+
return
46+
}
47+
48+
const result = new URL(currentRelativePath, selectedVersionBaseURL)
49+
result.hash = currentLocation.hash
50+
result.search = currentLocation.search
51+
return result
52+
}
53+
54+
// Basic string manipulation
55+
56+
/**
57+
*
58+
* @param s - string
59+
* @param prefix - prefix to strip
60+
*
61+
* @returns either the string with the prefix stripped or undefined if the
62+
* string did not begin with the prefix.
63+
*/
64+
export function stripPrefix(s: string, prefix: string): string | undefined {
65+
if (s.startsWith(prefix)) {
66+
return s.slice(prefix.length)
67+
}
68+
return undefined
69+
}
70+
71+
/**
72+
*
73+
* @param s1 - first string
74+
* @param s2 - second string
75+
*
76+
* @returns - the length of the longest common prefix of the two strings.
77+
*/
78+
function commonPrefixLen(s1: string, s2: string): number {
79+
const max = Math.min(s1.length, s2.length)
80+
let result
81+
for (result = 0; result < max; ++result) {
82+
if (s1[result] !== s2[result]) {
83+
break
84+
}
85+
}
86+
return result
87+
}
88+
89+
/**
90+
*
91+
* @param strs - an iterable of strings
92+
*
93+
* @returns the longest common prefix of all the strings
94+
*/
95+
export function shortestCommonPrefix(strs: Iterable<string>): string {
96+
let result // Undefined if no iterations happened
97+
for (const s of strs) {
98+
if (result === undefined) {
99+
result = s
100+
} else {
101+
result = result.slice(0, commonPrefixLen(result, s))
102+
}
103+
}
104+
return result ?? ""
37105
}

0 commit comments

Comments
 (0)