Skip to content

Commit

Permalink
Merge pull request #225 from AA-Turner/switchers/refactor
Browse files Browse the repository at this point in the history
  • Loading branch information
AA-Turner authored Oct 29, 2024
2 parents 1807c70 + 8cb6706 commit 757acb1
Showing 1 changed file with 176 additions and 174 deletions.
350 changes: 176 additions & 174 deletions templates/switchers.js
Original file line number Diff line number Diff line change
@@ -1,197 +1,199 @@
(function() {
'use strict';

if (!String.prototype.startsWith) {
Object.defineProperty(String.prototype, 'startsWith', {
value: function(search, rawPos) {
const pos = rawPos > 0 ? rawPos|0 : 0;
return this.substring(pos, pos + search.length) === search;
}
});
'use strict';

// File URIs must begin with either one or three forward slashes
const _is_file_uri = (uri) => uri.startsWith('file:/');

const _IS_LOCAL = _is_file_uri(window.location.href);
const _CURRENT_RELEASE = DOCUMENTATION_OPTIONS.VERSION || '';
const _CURRENT_VERSION = _CURRENT_RELEASE.split('.', 2).join('.');
const _CURRENT_LANGUAGE = DOCUMENTATION_OPTIONS.LANGUAGE?.toLowerCase() || 'en';
const _CURRENT_PREFIX = (() => {
if (_IS_LOCAL) return null;
// Sphinx 7.2+ defines the content root data attribute in the HTML element.
const _CONTENT_ROOT = document.documentElement.dataset.content_root;
if (_CONTENT_ROOT !== undefined) {
return new URL(_CONTENT_ROOT, window.location).pathname;
}
// Fallback for older versions of Sphinx (used in Python 3.10 and older).
const _NUM_PREFIX_PARTS = _CURRENT_LANGUAGE === 'en' ? 2 : 3;
return window.location.pathname.split('/', _NUM_PREFIX_PARTS).join('/') + '/';
})();

// Parses versions in URL segments like:
// "3", "dev", "release/2.7" or "3.6rc2"
const version_regexs = [
'(?:\\d)',
'(?:\\d\\.\\d[\\w\\d\\.]*)',
'(?:dev)',
'(?:release/\\d.\\d[\\x\\d\\.]*)'];

const all_versions = $VERSIONS;
const all_languages = $LANGUAGES;

function quote_attr(str) {
return '"' + str.replace('"', '\\"') + '"';
const _ALL_VERSIONS = new Map(Object.entries($VERSIONS));
const _ALL_LANGUAGES = new Map(Object.entries($LANGUAGES));

/**
* @param {Map<string, string>} versions
* @returns {HTMLSelectElement}
* @private
*/
const _create_version_select = (versions) => {
const select = document.createElement('select');
select.className = 'version-select';
if (_IS_LOCAL) {
select.disabled = true;
select.title = 'Version switching is disabled in local builds';
}

function build_version_select(release) {
let buf = ['<select id="version_select" aria-label="Python version">'];
const major_minor = release.split(".").slice(0, 2).join(".");

Object.entries(all_versions).forEach(function([version, title]) {
if (version === major_minor) {
buf.push('<option value=' + quote_attr(version) + ' selected="selected">' + release + '</option>');
} else {
buf.push('<option value=' + quote_attr(version) + '>' + title + '</option>');
}
});
for (const [version, title] of versions) {
const option = document.createElement('option');
option.value = version;
if (version === _CURRENT_VERSION) {
option.text = _CURRENT_RELEASE;
option.selected = true;
} else {
option.text = title;
}
select.add(option);
}

buf.push('</select>');
return buf.join('');
return select;
};

/**
* @param {Map<string, string>} languages
* @returns {HTMLSelectElement}
* @private
*/
const _create_language_select = (languages) => {
if (!languages.has(_CURRENT_LANGUAGE)) {
// In case we are browsing a language that is not yet in languages.
languages.set(_CURRENT_LANGUAGE, _CURRENT_LANGUAGE);
}

function build_language_select(current_language) {
let buf = ['<select id="language_select" aria-label="Language">'];
const select = document.createElement('select');
select.className = 'language-select';
if (_IS_LOCAL) {
select.disabled = true;
select.title = 'Language switching is disabled in local builds';
}

Object.entries(all_languages).forEach(function([language, title]) {
if (language === current_language) {
buf.push('<option value="' + language + '" selected="selected">' + title + '</option>');
} else {
buf.push('<option value="' + language + '">' + title + '</option>');
}
});
if (!(current_language in all_languages)) {
// In case we're browsing a language that is not yet in all_languages.
buf.push('<option value="' + current_language + '" selected="selected">' +
current_language + '</option>');
all_languages[current_language] = current_language;
}
buf.push('</select>');
return buf.join('');
for (const [language, title] of languages) {
const option = document.createElement('option');
option.value = language;
option.text = title;
if (language === _CURRENT_LANGUAGE) option.selected = true;
select.add(option);
}

function navigate_to_first_existing(urls) {
// Navigate to the first existing URL in urls.
const url = urls.shift();
if (urls.length == 0 || url.startsWith("file:///")) {
window.location.href = url;
return;
}
return select;
};

/**
* Change the current page to the first existing URL in the list.
* @param {Array<string>} urls
* @private
*/
const _navigate_to_first_existing = (urls) => {
// Navigate to the first existing URL in urls.
for (const url of urls) {
fetch(url)
.then(function(response) {
.then((response) => {
if (response.ok) {
window.location.href = url;
} else {
navigate_to_first_existing(urls);
return url;
}
})
.catch(function(error) {
navigate_to_first_existing(urls);
.catch((err) => {
console.error(`Error when fetching '${url}'!`);
console.error(err);
});
}

function on_version_switch() {
const selected_version = this.options[this.selectedIndex].value + '/';
const url = window.location.href;
const current_language = language_segment_from_url();
const current_version = version_segment_from_url();
const new_url = url.replace('/' + current_language + current_version,
'/' + current_language + selected_version);
if (new_url != url) {
navigate_to_first_existing([
new_url,
url.replace('/' + current_language + current_version,
'/' + selected_version),
'/' + current_language + selected_version,
'/' + selected_version,
'/'
]);
}
}

function on_language_switch() {
let selected_language = this.options[this.selectedIndex].value + '/';
const url = window.location.href;
const current_language = language_segment_from_url();
const current_version = version_segment_from_url();
if (selected_language == 'en/') // Special 'default' case for English.
selected_language = '';
let new_url = url.replace('/' + current_language + current_version,
'/' + selected_language + current_version);
if (new_url != url) {
navigate_to_first_existing([
new_url,
'/'
]);
}
// if all else fails, redirect to the d.p.o root
window.location.href = '/';
return '/';
};

/**
* Callback for the version switcher.
* @param {Event} event
* @returns {void}
* @private
*/
const _on_version_switch = (event) => {
if (_IS_LOCAL) return;

const selected_version = event.target.value;
// English has no language prefix.
const new_prefix_en = `/${selected_version}/`;
const new_prefix =
_CURRENT_LANGUAGE === 'en'
? new_prefix_en
: `/${_CURRENT_LANGUAGE}/${selected_version}/`;
if (_CURRENT_PREFIX !== new_prefix) {
// Try the following pages in order:
// 1. The current page in the current language with the new version
// 2. The current page in English with the new version
// 3. The documentation home in the current language with the new version
// 4. The documentation home in English with the new version
_navigate_to_first_existing([
window.location.href.replace(_CURRENT_PREFIX, new_prefix),
window.location.href.replace(_CURRENT_PREFIX, new_prefix_en),
new_prefix,
new_prefix_en,
]);
}

// Returns the path segment of the language as a string, like 'fr/'
// or '' if not found.
function language_segment_from_url() {
const path = window.location.pathname;
const language_regexp = '/((?:' + Object.keys(all_languages).join("|") + ')/)'
const match = path.match(language_regexp);
if (match !== null)
return match[1];
return '';
};

/**
* Callback for the language switcher.
* @param {Event} event
* @returns {void}
* @private
*/
const _on_language_switch = (event) => {
if (_IS_LOCAL) return;

const selected_language = event.target.value;
// English has no language prefix.
const new_prefix =
selected_language === 'en'
? `/${_CURRENT_VERSION}/`
: `/${selected_language}/${_CURRENT_VERSION}/`;
if (_CURRENT_PREFIX !== new_prefix) {
// Try the following pages in order:
// 1. The current page in the new language with the current version
// 2. The documentation home in the new language with the current version
_navigate_to_first_existing([
window.location.href.replace(_CURRENT_PREFIX, new_prefix),
new_prefix,
]);
}

// Returns the path segment of the version as a string, like '3.6/'
// or '' if not found.
function version_segment_from_url() {
const path = window.location.pathname;
const language_segment = language_segment_from_url();
const version_segment = '(?:(?:' + version_regexs.join('|') + ')/)';
const version_regexp = language_segment + '(' + version_segment + ')';
const match = path.match(version_regexp);
if (match !== null)
return match[1];
return ''
}

function create_placeholders_if_missing() {
const version_segment = version_segment_from_url();
const language_segment = language_segment_from_url();
const index = "/" + language_segment + version_segment;

if (document.querySelectorAll('.version_switcher_placeholder').length > 0) {
return;
}

const html = '<span class="language_switcher_placeholder"></span> \
<span class="version_switcher_placeholder"></span> \
<a href="/" id="indexlink">Documentation</a> &#187;';

const probable_places = [
"body>div.related>ul>li:not(.right):contains('Documentation'):first",
"body>div.related>ul>li:not(.right):contains('documentation'):first",
];

for (let i = 0; i < probable_places.length; i++) {
let probable_place = $(probable_places[i]);
if (probable_place.length == 1) {
probable_place.html(html);
document.getElementById('indexlink').href = index;
return;
}
}
}

document.addEventListener('DOMContentLoaded', function() {
const language_segment = language_segment_from_url();
const current_language = language_segment.replace(/\/+$/g, '') || 'en';
const version_select = build_version_select(DOCUMENTATION_OPTIONS.VERSION);

create_placeholders_if_missing();

let placeholders = document.querySelectorAll('.version_switcher_placeholder');
placeholders.forEach(function(placeholder) {
placeholder.innerHTML = version_select;

let selectElement = placeholder.querySelector('select');
selectElement.addEventListener('change', on_version_switch);
};

/**
* Initialisation function for the version and language switchers.
* @returns {void}
* @private
*/
const _initialise_switchers = () => {
const versions = _ALL_VERSIONS;
const languages = _ALL_LANGUAGES;

const version_select = _create_version_select(versions);
document
.querySelectorAll('.version_switcher_placeholder')
.forEach((placeholder) => {
const s = version_select.cloneNode(true);
s.addEventListener('change', _on_version_switch);
placeholder.append(s);
placeholder.classList.remove('version_switcher_placeholder');
});

const language_select = build_language_select(current_language);

placeholders = document.querySelectorAll('.language_switcher_placeholder');
placeholders.forEach(function(placeholder) {
placeholder.innerHTML = language_select;

let selectElement = placeholder.querySelector('select');
selectElement.addEventListener('change', on_language_switch);
const language_select = _create_language_select(languages);
document
.querySelectorAll('.language_switcher_placeholder')
.forEach((placeholder) => {
const s = language_select.cloneNode(true);
s.addEventListener('change', _on_language_switch);
placeholder.append(s);
placeholder.classList.remove('language_switcher_placeholder');
});
});
})();
};

if (document.readyState !== 'loading') {
_initialise_switchers();
} else {
document.addEventListener('DOMContentLoaded', _initialise_switchers);
}

0 comments on commit 757acb1

Please sign in to comment.