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

✨ ENH: Support version and locale switcher #360

Closed
wants to merge 2 commits into from
Closed
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
7 changes: 7 additions & 0 deletions pydata_sphinx_theme/_templates/current-version.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<ul class="navbar-nav">
<li class="nav-item">
<span id="current-switcher">
{{version}}
</span>
</li>
</ul>
36 changes: 36 additions & 0 deletions pydata_sphinx_theme/_templates/version-switcher.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<script type="text/javascript">
(function () {
window.versionSwitcher = {
pageName: "{{pagename}}.html",
versionJsonUrl: "{{theme_version_switch_json_url}}",
enableLocaleSupport: "{{theme_version_switch_enable_locale}}" === "True",
// TODO read from "{{theme_version_switch_locales}}"
allLocales: [
{
"locale": "zh",
"display": "中文"
},
{
"locale": "en",
"display": "EN"
}
]
}
})();
</script>

<ul class="navbar-nav">
<li class="nav-item dropdown">
<button id="version-dropdown" class="btn btn-secondary btn-sm dropdown-toggle" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<!-- placeholder for javascript filling above -->
</button>
<div id="version-menu" class="dropdown-menu" style="min-width: 6rem;">
<!-- placeholder for javascript filling above -->
</div>
</li>
<li class="nav-item">
<span id="locale-switcher">
<!-- placeholder for locale switcher -->
</span>
</li>
</ul>

Large diffs are not rendered by default.

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions pydata_sphinx_theme/static/webpack-macros.html
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,13 @@

{% macro head_pre_bootstrap() %}
<link href="{{ pathto('_static/css/theme.css', 1) }}" rel="stylesheet" />
<link href="{{ pathto('_static/css/index.c5995385ac14fb8791e8eb36b4908be2.css', 1) }}" rel="stylesheet" />
<link href="{{ pathto('_static/css/index.58aa5513ec6289eeac05a5a62c8d7762.css', 1) }}" rel="stylesheet" />
{% endmacro %}

{% macro head_js_preload() %}
<link rel="preload" as="script" href="{{ pathto('_static/js/index.1c5a1a01449ed65a7b51.js', 1) }}">
<link rel="preload" as="script" href="{{ pathto('_static/js/index.c112c1b0a1389e113653.js', 1) }}">
{% endmacro %}

{% macro body_post() %}
<script src="{{ pathto('_static/js/index.1c5a1a01449ed65a7b51.js', 1) }}"></script>
<script src="{{ pathto('_static/js/index.c112c1b0a1389e113653.js', 1) }}"></script>
{% endmacro %}
6 changes: 5 additions & 1 deletion pydata_sphinx_theme/theme.conf
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,8 @@ navbar_start = navbar-logo.html
navbar_center = navbar-nav.html
navbar_end = navbar-icon-links.html
footer_items = copyright.html, sphinx-version.html
page_sidebar_items = page-toc.html, edit-this-page.html
page_sidebar_items = page-toc.html, edit-this-page.html
use_version_switch = True

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this (and enable_locale) should probably default to False start as an opt-in in case other people are already rolling their own version switcher?

version_switch_json_url = /versions.json
version_switch_enable_locale = True
version_switch_locales = zh, en
202 changes: 201 additions & 1 deletion src/js/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,208 @@ function scrollToActive() {
});
}


$(document).ready(() => {
scrollToActive();
addTOCInteractivity();
});

function setupVersionSwitcher() {
// Setup Version Switcher

// Only enable version switcher when window.versionSwitcher is filled by sphinx
if (!window.versionSwitcher) {
return;
}
let pageName = window.versionSwitcher.pageName;
let versionJsonUrl = window.versionSwitcher.versionJsonUrl;
let enableLocaleSupport = window.versionSwitcher.enableLocaleSupport;
let allLocales = window.versionSwitcher.allLocales;

// Remote version should like this.
// .name and .alias must be unique in each locale.
// It's not necessary to have same versions in all locales.
// 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.

/*
let allVersions = {
"en": [
{
"name": "v1.2.0",
"url": "v1.2.0",
"alias": ["latest"],
"default": true
},
{
"name": "v1.1.0",
"url": "v1.1.0",
"alias": []
},
],
"zh":[
{
"name": "v1.0.0",
"url": "v1.0.0",
"alias": []
"default": true
},
],
};
*/
function parseCurrentURL() {
// parseCurrentURL look up current pathname, generate all information about current version and locale.

let pathname = window.location.pathname;

// add "index.html" back when browser omit it.
if (pageName.endsWith("index.html") && pathname.endsWith("/")) {
pathname += "index.html";
}
if (pathname.slice(-pageName.length) !== pageName) {
// Sphinx generated pages should have exactly same suffix
throw 'page suffix do not match requirements';

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want to throw here (and below)? Or do, we want to just fail gracefully somehow. If any of the parsing failed, maybe remove the version switcher element altogether? Or fill it with a go-to-home button instead. I'm not sure what the best path is.

}

// Get base URL by Removing '/' and pageName
let baseURL = pathname.slice(0, -(pageName.length + 1));
let parts = baseURL.split('/');

let currentVersion = '';
let currentLocale = '';

if (enableLocaleSupport) {
if (parts.length < 1) {
throw 'page base URL do not have any locale information';
}
currentLocale = parts.pop();
}
if (parts.length < 1) {
throw 'page base URL do not have any locale information';
}
currentVersion = parts.pop();
// This is base URL without any version or locate.
let globalBaseURL = parts.join('/')

return {pageName, baseURL, currentVersion, currentLocale, globalBaseURL};

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When I was testing this locally, I was getting an empty string for currentVersion, which caused everything downstream to fail for me. I think this may be because the project is serving up the html build directory /index.html and not /en/v1.3/index.html. I'm not sure if this has to do with Sphinx options when I set up the project or not, but I think this will probably need to be robust against local builds for testing too where the URL may not be fully populated with locale and version.

}

// validate Check currentLocale and currentVersion is valid.
// Return canonicalVersion: indicate current version's real name
function validate(allVersions, info) {
let locale = "default"; // Use default as key when locale feature is disabled
if (enableLocaleSupport) {
locale = info.currentLocale;
}
let version_list = allVersions[locale];

if (version_list === undefined) {
throw `locale '${locale}'doesn't exist in remote version mapping`;
}

let canonicalVersion = function() { // Match currentVersion in version_list, try to find canonical version name by matching name and alias
for (const v of version_list) {
if (info.currentVersion === v.name) {
return v.name;
}
for (const alias_name of v.alias) {
if (info.currentVersion === alias_name) {
return v.name;
}
}
}
throw `version '${info.currentVersion}' doesn't exist in remove version maaping`
}()

return canonicalVersion;
}

// Set locale or version to null to indicate unchanged property.
function constructUrl(info, targetLocale, targetVersion) {
let segments = [info.globalBaseURL];

if (targetLocale == null) {
targetLocale = info.currentLocale;
}
if (targetVersion == null) {
targetVersion = info.currentVersion;
}
segments.push(targetVersion);
if (enableLocaleSupport) {
segments.push(targetLocale);
}
segments.push(info.pageName);
return segments.join('/') + window.location.hash;
}

function render(allVersions, info) {
function onSwitchVersion(evt) {
evt.preventDefault()
let selected = evt.currentTarget.getAttribute('key');

// process with alias problem, e.g. do not jump if target is just an alias of current one.
if (selected == info.canonicalVersion) {
// Current page is already the target version, ignore
return;
}

let new_url = constructUrl(info, null, selected);
window.location.assign(new_url);
}

function onSwitchLocale(evt) {
evt.preventDefault()
let selected = evt.currentTarget.getAttribute('key');

let new_url = constructUrl(info, selected, null);
window.location.assign(new_url);
}

// Fill the current version in the dropdown, always show real name instead of alias
document.getElementById("version-dropdown").innerText = info.canonicalVersion;

const menuHTML = (function() {
return allVersions[info.currentLocale].map((version) => {
let text = version.name;
if (version.alias.length > 0) {
text = `${version.name} (${version.alias.join(' ')})`
}

return `<button class="dropdown-item" key="${version.name}">${text}</button>`
})
})().join('')
// fill the version menu
document.getElementById("version-menu").innerHTML = menuHTML;

// bind the changes to this menu to trigger the switching function
$('#version-menu button').on('click', onSwitchVersion)

// Adding locale switcher
const localeHTML = (function() {
return allLocales.map((l) => {
if (l.locale === info.currentLocale) {
return `<a class="locale-btn locale-current" key="${l.locale}">${l.display}</a>`
} else {
return `<a class="locale-btn locale-option "key="${l.locale}">${l.display}</a>`
}
})
})().join('/')
document.getElementById("locale-switcher").innerHTML = localeHTML;

$('#locale-switcher .locale-option').on('click', onSwitchLocale)
}

// Trigger fetch as earlier as possible to speedup page loading.
let p = fetch(versionJsonUrl).then((resp) => {
return resp.json()
});

let info = parseCurrentURL();

p.then((allVersions) => {
let canonicalVersion = validate(allVersions, info);
info.canonicalVersion = canonicalVersion;

render(allVersions, info);
})
}

$(document).ready(setupVersionSwitcher);
10 changes: 10 additions & 0 deletions src/scss/_navbar.scss
Original file line number Diff line number Diff line change
Expand Up @@ -55,3 +55,13 @@
.navbar-header a {
padding: 0 15px;
}

.locale-btn {
padding: 0 5px !important;
&.locale-current {
color: #AAA;
}
&.locale-option {
cursor: pointer;
}
}