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

feat(version-switcher): add version switcher #433

Closed
wants to merge 3 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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
21 changes: 21 additions & 0 deletions pydata_sphinx_theme/_templates/version-switcher.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<script type="text/javascript">
(function () {
window.switcher = {
pageName: "{{pagename}}.html",
switchers: JSON.parse("{{theme_switchers}}".replaceAll("'", '"')),
};
window.configUrl = "{{theme_config_json_url}}";
})();
</script>
{%- if 'version' in theme_switchers %}
<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>
</ul>
{%- endif %}
32 changes: 0 additions & 32 deletions pydata_sphinx_theme/static/js/index.1c5a1a01449ed65a7b51.js

This file was deleted.

43 changes: 43 additions & 0 deletions pydata_sphinx_theme/static/js/index.da3a67c4e9d006c31e16.js

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions pydata_sphinx_theme/static/webpack-macros.html
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@
{% 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.da3a67c4e9d006c31e16.js', 1) }}">
{% endmacro %}

{% macro body_post() %}
<script src="{{ pathto('_static/js/index.1c5a1a01449ed65a7b51.js', 1) }}"></script>
<script src="{{ pathto('_static/js/index.da3a67c4e9d006c31e16.js', 1) }}"></script>
{% endmacro %}
5 changes: 4 additions & 1 deletion pydata_sphinx_theme/theme.conf
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,7 @@ 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
config_json_url = /config.json
use_version_switch = False
switchers = []
159 changes: 157 additions & 2 deletions src/js/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import 'popper.js';
import 'bootstrap';

import './../scss/index.scss';
import { name, part } from 'file-loader';

function addTOCInteractivity() {
// TOC sidebar - add "active" class to parent list
Expand All @@ -36,7 +37,6 @@ function addTOCInteractivity() {
});
}


// Navigation sidebar scrolling to active page
function scrollToActive() {
var sidebar = document.getElementById('bd-docs-nav')
Expand Down Expand Up @@ -72,8 +72,163 @@ function scrollToActive() {
});
}

function parseCurrentURL(switchers_enable) {
// parseCurrentURL look up current pathname, generate current version locale

let pathname = window.location.pathname;
let pageName = window.switcher.pageName;

// 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'
}

let baseURL = pathname.slice(0, -(pageName.length + 1));
let parts = baseURL.split("/");
let switcherInfo = {};
// find switcher info
for(let switchidx=0; switchidx<switchers_enable.length; switchidx++) {
let switchName = switchers_enable[switchidx];
switcherInfo[switchName+"HeadURL"] = parts.slice(0,parts.length-switchidx-1).join('/');
switcherInfo[switchName+"URL"] = parts[parts.length-switchidx-1];
if(switchidx===0) {
switcherInfo[switchName+"TailURL"] = pageName
} else {
switcherInfo[switchName+"TailURL"] = parts.slice(parts.length-switchidx).join('/')+"/"+pageName;
}
}

return switcherInfo
}

function setupVersionSwitcher(allVersions, versionHeadURL, versionURL, versionTailURL) {
// Setup Version Switcher

// version switcher's config should be a (key, value) pair of config.json:
// 'items' is a list of all versions. `name` and `labels` must be unique.
// You should specify a default version(both name and labels are ok).
/*
let configs = {
"items": [
{
"name": "v1.0",
"url": "1.0",
"labels": []
},
{
"name": "v1.3",
"url": "1.3",
"labels": ["stable"]
},
{
"name": "v1.4",
"url": "1.4",
"labels": ["latest"]
},
],
"default": "stable"
}
*/


// validate Check currentVersion is valid.
// Return version's name
function validate(allVersions, versionURL) {
for(const version of allVersions) {
if((typeof version === "string") && (version === versionURL)) {
return version
} else if(typeof version === "object" && ((version.name === versionURL) || (version.label === versionURL))) {
return version.name
}
}

throw `version '${versionURL}' doesn't exist in remove version mapping`
}

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.currentVersion.url) {
// Current page is already the target version, ignore
return;
}

let new_url = info.versionHeadURL+"/"+selected+"/"+info.versionTailURL;
$.ajax({
url: new_url,
success: function () {
window.location.assign(new_url);
},
error: function () {
new_url = info.versionHeadURL+"/"+selected+"/";
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.currentVersion;

const menuHTML = (function() {
return allVersions.map((version) => {
let name = '';
let label = '';
if(typeof version === 'string') {
name = version
} else {
name = version.name
label = '('+version.label+')'
}

return `<button class="dropdown-item" key="${name}">${name+label}</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)
}

let currentVersion = validate(allVersions, versionURL);
let info = {versionHeadURL, versionURL, versionTailURL, currentVersion}
render(allVersions, info)
}

function applyConfig() {
let switchers_enable = window.switcher.switchers;

if(switchers_enable.length === 0) return;

let configUrl = window.configUrl;
fetch(configUrl).then((resp) => {
return resp.json()
}).then((configs) => {
let info = parseCurrentURL(switchers_enable);

if(switchers_enable.indexOf("version") !== -1) {
// enable version switcher
setupVersionSwitcher(configs["version"], info.versionHeadURL, info.versionURL, info.versionTailURL)
}
}).catch((error) => {
throwBtnError("version-dropdown", error)
})
}

function throwBtnError(btnID, errMsg) {
$("#"+btnID).addClass("btn-danger").prop("disabled", true).text("Error!!!");
throw errMsg
}

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