Skip to content

Commit 3df5d35

Browse files
committed
feature: add version switcher
1 parent b822548 commit 3df5d35

8 files changed

+266
-7
lines changed

pydata_sphinx_theme/docs-navbar.html

+7-1
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,13 @@
4141
{% endif %}
4242

4343
{%- block icon_links -%}
44-
{%- include "icon-links.html" with context -%}
44+
{%- include "icon-links.html" with context -%}
4545
{%- endblock %}
46+
47+
{% if theme_use_version_switch == true %}
48+
{%- include "version-switcher.html" %}
49+
{% endif %}
50+
</li>
51+
</ul>
4652
</div>
4753
</div>

pydata_sphinx_theme/static/css/index.987b06ff42468e43a77c64bd883678fc.css renamed to pydata_sphinx_theme/static/css/index.93dda2a1e4f2b831d8345b5b3dbee4ea.css

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pydata_sphinx_theme/static/js/index.c681211bbbd497c597b1.js renamed to pydata_sphinx_theme/static/js/index.3c6125c0ae68274ddd1b.js

+2-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pydata_sphinx_theme/static/webpack-macros.html

+3-3
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,13 @@
1313

1414
{% macro head_pre_bootstrap() %}
1515
<link href="{{ pathto('_static/css/theme.css', 1) }}" rel="stylesheet" />
16-
<link href="{{ pathto('_static/css/index.987b06ff42468e43a77c64bd883678fc.css', 1) }}" rel="stylesheet" />
16+
<link href="{{ pathto('_static/css/index.93dda2a1e4f2b831d8345b5b3dbee4ea.css', 1) }}" rel="stylesheet" />
1717
{% endmacro %}
1818

1919
{% macro head_js_preload() %}
20-
<link rel="preload" as="script" href="{{ pathto('_static/js/index.c681211bbbd497c597b1.js', 1) }}">
20+
<link rel="preload" as="script" href="{{ pathto('_static/js/index.3c6125c0ae68274ddd1b.js', 1) }}">
2121
{% endmacro %}
2222

2323
{% macro body_post() %}
24-
<script src="{{ pathto('_static/js/index.c681211bbbd497c597b1.js', 1) }}"></script>
24+
<script src="{{ pathto('_static/js/index.3c6125c0ae68274ddd1b.js', 1) }}"></script>
2525
{% endmacro %}

pydata_sphinx_theme/theme.conf

+5
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,8 @@ search_bar_position = sidebar
2121
navigation_with_keys = True
2222
show_toc_level = 1
2323
navbar_align = content
24+
use_version_switch = True
25+
version_switch_json_url = /versions.json
26+
version_switch_enable_locale = True
27+
version_switch_locales = zh, en
28+
+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
<script type="text/javascript">
2+
(function () {
3+
window.versionSwitcher = {
4+
pageName: "{{pagename}}.html",
5+
versionJsonUrl: "{{theme_version_switch_json_url}}",
6+
enableLocaleSupport: "{{theme_version_switch_enable_locale}}" === "True",
7+
// TODO read from "{{theme_version_switch_locales}}"
8+
allLocales: [
9+
{
10+
"locale": "zh",
11+
"display": "中文"
12+
},
13+
{
14+
"locale": "en",
15+
"display": "EN"
16+
}
17+
]
18+
}
19+
})();
20+
</script>
21+
22+
<ul class="navbar-nav">
23+
<li class="nav-item dropdown">
24+
<button id="version-dropdown" class="btn btn-secondary btn-sm dropdown-toggle" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
25+
<!-- placeholder for javascript filling above -->
26+
</button>
27+
<div id="version-menu" class="dropdown-menu" style="min-width: 6rem;">
28+
<!-- placeholder for javascript filling above -->
29+
</div>
30+
</li>
31+
<li class="nav-item">
32+
<span id="locale-switcher">
33+
<!-- placeholder for locale switcher -->
34+
</span>
35+
</li>
36+
</ul>

src/js/index.js

+202
Original file line numberDiff line numberDiff line change
@@ -39,3 +39,205 @@ function addTOCInteractivity() {
3939
$(document).ready(() => {
4040
addTOCInteractivity();
4141
});
42+
43+
44+
function setupVersionSwitcher() {
45+
// Setup Version Switcher
46+
47+
// Only enable version switcher when window.versionSwitcher is filled by sphinx
48+
if (!window.versionSwitcher) {
49+
return;
50+
}
51+
let pageName = window.versionSwitcher.pageName;
52+
let versionJsonUrl = window.versionSwitcher.versionJsonUrl;
53+
let enableLocaleSupport = window.versionSwitcher.enableLocaleSupport;
54+
let allLocales = window.versionSwitcher.allLocales;
55+
56+
// Remote version should like this.
57+
// .name and .alias must be unique in each locale.
58+
// It's not necessary to have same versions in all locales.
59+
// When locale is enabled, there must be only one .default=true item in each locale to indicate which one should be redirect if target version doesn't exist in target locale.
60+
61+
/*
62+
let allVersions = {
63+
"en": [
64+
{
65+
"name": "v1.2.0",
66+
"url": "v1.2.0",
67+
"alias": ["latest"],
68+
"default": true
69+
},
70+
{
71+
"name": "v1.1.0",
72+
"url": "v1.1.0",
73+
"alias": []
74+
},
75+
],
76+
"zh":[
77+
{
78+
"name": "v1.0.0",
79+
"url": "v1.0.0",
80+
"alias": []
81+
"default": true
82+
},
83+
],
84+
};
85+
*/
86+
function parseCurrentURL() {
87+
// parseCurrentURL look up current pathname, generate all information about current version and locale.
88+
89+
let pathname = window.location.pathname;
90+
91+
// add "index.html" back when browser omit it.
92+
if (pageName.endsWith("index.html") && pathname.endsWith("/")) {
93+
pathname += "index.html";
94+
}
95+
if (pathname.slice(-pageName.length) !== pageName) {
96+
// Sphinx generated pages should have exactly same suffix
97+
throw 'page suffix do not match requirements';
98+
}
99+
100+
// Get base URL by Removing '/' and pageName
101+
let baseURL = pathname.slice(0, -(pageName.length + 1));
102+
let parts = baseURL.split('/');
103+
104+
let currentVersion = '';
105+
let currentLocale = '';
106+
107+
if (enableLocaleSupport) {
108+
if (parts.length < 1) {
109+
throw 'page base URL do not have any locale information';
110+
}
111+
currentLocale = parts.pop();
112+
}
113+
if (parts.length < 1) {
114+
throw 'page base URL do not have any locale information';
115+
}
116+
currentVersion = parts.pop();
117+
// This is base URL without any version or locate.
118+
let globalBaseURL = parts.join('/')
119+
120+
return {pageName, baseURL, currentVersion, currentLocale, globalBaseURL};
121+
}
122+
123+
// validate Check currentLocale and currentVersion is valid.
124+
// Return canonicalVersion: indicate current version's real name
125+
function validate(allVersions, info) {
126+
let locale = "default"; // Use default as key when locale feature is disabled
127+
if (enableLocaleSupport) {
128+
locale = info.currentLocale;
129+
}
130+
let version_list = allVersions[locale];
131+
132+
if (version_list === undefined) {
133+
throw `locale '${locale}'doesn't exist in remote version mapping`;
134+
}
135+
136+
let canonicalVersion = function() { // Match currentVersion in version_list, try to find canonical version name by matching name and alias
137+
for (const v of version_list) {
138+
if (info.currentVersion === v.name) {
139+
return v.name;
140+
}
141+
for (const alias_name of v.alias) {
142+
if (info.currentVersion === alias_name) {
143+
return v.name;
144+
}
145+
}
146+
}
147+
throw `version '${info.currentVersion}' doesn't exist in remove version maaping`
148+
}()
149+
150+
return canonicalVersion;
151+
}
152+
153+
// Set locale or version to null to indicate unchanged property.
154+
function constructUrl(info, targetLocale, targetVersion) {
155+
let segments = [info.globalBaseURL];
156+
157+
if (targetLocale == null) {
158+
targetLocale = info.currentLocale;
159+
}
160+
if (targetVersion == null) {
161+
targetVersion = info.currentVersion;
162+
}
163+
segments.push(targetVersion);
164+
if (enableLocaleSupport) {
165+
segments.push(targetLocale);
166+
}
167+
segments.push(info.pageName);
168+
return segments.join('/') + window.location.hash;
169+
}
170+
171+
function render(allVersions, info) {
172+
function onSwitchVersion(evt) {
173+
evt.preventDefault()
174+
let selected = evt.currentTarget.getAttribute('key');
175+
176+
// process with alias problem, e.g. do not jump if target is just an alias of current one.
177+
if (selected == info.canonicalVersion) {
178+
// Current page is already the target version, ignore
179+
return;
180+
}
181+
182+
let new_url = constructUrl(info, null, selected);
183+
window.location.assign(new_url);
184+
}
185+
186+
function onSwitchLocale(evt) {
187+
evt.preventDefault()
188+
let selected = evt.currentTarget.getAttribute('key');
189+
190+
let new_url = constructUrl(info, selected, null);
191+
window.location.assign(new_url);
192+
}
193+
194+
// Fill the current version in the dropdown, always show real name instead of alias
195+
document.getElementById("version-dropdown").innerText = info.canonicalVersion;
196+
197+
const menuHTML = (function() {
198+
return allVersions[info.currentLocale].map((version) => {
199+
let text = version.name;
200+
if (version.alias.length > 0) {
201+
text = `${version.name} (${version.alias.join(' ')})`
202+
}
203+
204+
return `<button class="dropdown-item" key="${version.name}">${text}</button>`
205+
})
206+
})().join('')
207+
// fill the version menu
208+
document.getElementById("version-menu").innerHTML = menuHTML;
209+
210+
// bind the changes to this menu to trigger the switching function
211+
$('#version-menu button').on('click', onSwitchVersion)
212+
213+
// Adding locale switcher
214+
const localeHTML = (function() {
215+
return allLocales.map((l) => {
216+
if (l.locale === info.currentLocale) {
217+
return `<a class="locale-btn locale-current" key="${l.locale}">${l.display}</a>`
218+
} else {
219+
return `<a class="locale-btn locale-option "key="${l.locale}">${l.display}</a>`
220+
}
221+
})
222+
})().join('/')
223+
document.getElementById("locale-switcher").innerHTML = localeHTML;
224+
225+
$('#locale-switcher .locale-option').on('click', onSwitchLocale)
226+
}
227+
228+
// Trigger fetch as earlier as possible to speedup page loading.
229+
let p = fetch(versionJsonUrl).then((resp) => {
230+
return resp.json()
231+
});
232+
233+
let info = parseCurrentURL();
234+
235+
p.then((allVersions) => {
236+
let canonicalVersion = validate(allVersions, info);
237+
info.canonicalVersion = canonicalVersion;
238+
239+
render(allVersions, info);
240+
})
241+
}
242+
243+
$(document).ready(setupVersionSwitcher);

src/scss/_navbar.scss

+10
Original file line numberDiff line numberDiff line change
@@ -48,3 +48,13 @@
4848
.navbar-header a {
4949
padding: 0 15px;
5050
}
51+
52+
.locale-btn {
53+
padding: 0 5px !important;
54+
&.locale-current {
55+
color: #AAA;
56+
}
57+
&.locale-option {
58+
cursor: pointer;
59+
}
60+
}

0 commit comments

Comments
 (0)