diff --git a/src/bootstrap/doc.rs b/src/bootstrap/doc.rs
index 819af6587484d..d6cc01232a7dd 100644
--- a/src/bootstrap/doc.rs
+++ b/src/bootstrap/doc.rs
@@ -430,13 +430,21 @@ impl Step for Std {
t!(fs::create_dir_all(&out));
t!(fs::copy(builder.src.join("src/doc/rust.css"), out.join("rust.css")));
+ t!(fs::copy(
+ builder.src.join("src/doc/version-switcher.js"),
+ out.join("version-switcher.js")
+ ));
+
let index_page = builder.src.join("src/doc/index.md").into_os_string();
+ let switcher_script = builder.src.join("src/doc/switcher.inc").into_os_string();
let mut extra_args = vec![
OsStr::new("--markdown-css"),
OsStr::new("rust.css"),
OsStr::new("--markdown-no-toc"),
OsStr::new("--index-page"),
&index_page,
+ OsStr::new("--html-in-header"),
+ &switcher_script,
];
if !builder.config.docs_minification {
diff --git a/src/doc/switcher.inc b/src/doc/switcher.inc
new file mode 100644
index 0000000000000..420dcb5c58ec8
--- /dev/null
+++ b/src/doc/switcher.inc
@@ -0,0 +1 @@
+
diff --git a/src/doc/version-switcher.js b/src/doc/version-switcher.js
new file mode 100644
index 0000000000000..94a84deda2bd6
--- /dev/null
+++ b/src/doc/version-switcher.js
@@ -0,0 +1,269 @@
+(function() {
+
+let CURRENT_VERSION = -1;
+
+function get_current_version() {
+ if (CURRENT_VERSION !== -1) {
+ return CURRENT_VERSION;
+ }
+ const now = Date.now();
+ // Month is 0-indexed.
+ // First release of Rust, 15 may 2015.
+ const first_release = new Date(2015, 4, 15);
+ const diff_time = Math.abs(now - first_release);
+ const nb_days = Math.ceil(diff_time / (1000 * 60 * 60 * 24));
+ const nb_weeks = nb_days / 7;
+ CURRENT_VERSION = Math.floor(nb_weeks / 6);
+ return CURRENT_VERSION;
+}
+
+function checkIfIsOldVersion() {
+ if (["http:", "https:"].indexOf(window.location.protocol) === -1) {
+ return false;
+ }
+ const parts = window.location.pathname.split("/");
+
+ return parts.length > 1 && parts[1].indexOf(".") !== -1 && parts[1] !== CURRENT_VERSION;
+}
+
+function createOption(text, isDefault) {
+ const option = document.createElement("option");
+ option.value = text;
+ option.innerText = text;
+ if (isDefault) {
+ option.selected = true;
+ }
+ return option;
+}
+
+function addStyle(css) {
+ const style = document.createElement("style");
+ style.type = "text/css";
+ style.appendChild(document.createTextNode(css));
+
+ document.head.appendChild(style);
+}
+
+function setupStyleFor60(rustdoc_container, switcherEl) {
+ function changeSidebarTop() {
+ const height = switcherEl.getBoundingClientRect().height;
+ const sidebarTopbar = document.querySelector(".mobile-topbar");
+ const sidebarTopbarHeight = sidebarTopbar.getBoundingClientRect().height;
+ const sidebar = document.querySelector(".sidebar");
+ sidebar.style.top = height + sidebarTopbarHeight + 1 + "px";
+ }
+ setTimeout(() => {
+ changeSidebarTop();
+ }, 0); // it'll be computed once it's added in the DOM.
+ window.addEventListener("resize", changeSidebarTop);
+}
+
+function setupStyleFor59(rustdoc_container, switcherEl) {
+ function changeSidebarTop() {
+ const height = switcherEl.getBoundingClientRect().height;
+ document.body.marginTop = height + 1 + "px";
+ const sidebarButton = document.querySelector(".sidebar-menu");
+ const val = sidebarButton.getAttribute("old-top");
+ if (val === null) {
+ // We update the position directly.
+ sidebarButton.style.top = height + 1 + "px";
+ } else {
+ // We update the attribute value.
+ sidebarButton.setAttribute("old-top", height + 1 + "px");
+ }
+ }
+ setTimeout(() => {
+ changeSidebarTop();
+ }, 0); // it'll be computed once it's added in the DOM.
+ window.addEventListener("resize", changeSidebarTop);
+ document.querySelector(".sidebar-menu").addEventListener("click", () => {
+ const sidebarButton = document.querySelector(".sidebar-menu");
+ const val = sidebarButton.getAttribute("old-top");
+ if (val === null) {
+ const height = switcherEl.getBoundingClientRect().height;
+ sidebarButton.setAttribute("old-top", sidebarButton.style.top);
+ sidebarButton.style.top = "0";
+ } else {
+ sidebarButton.style.top = val;
+ sidebarButton.removeAttribute("old-top");
+ }
+ });
+}
+
+function setupStyleFor32(rustdoc_container, switcherEl, extraStyle) {
+ document.body.style.padding = "0";
+ rustdoc_container.style.position = "relative";
+ rustdoc_container.style.padding = "0 15px 20px 15px";
+
+ addStyle(`@media (min-width: 701px) {
+ .rustdoc {
+ padding: 10px 15px 20px 15px !important;
+ }
+ #switch-version-filler {
+ display: block !important;
+ left: 0 !important;
+ }
+}
+
+.sidebar.mobile {
+ top: 0 !important;
+}
+${extraStyle}`);
+
+ // We also need to create a "cosmetic" element to not have a weird empty space above the
+ // sidebar.
+ const filler = document.createElement("div");
+ filler.style.position = "fixed";
+ filler.style.top = "0";
+ filler.style.bottom = "0";
+ filler.style.zIndex = "-1";
+ filler.style.display = "none";
+ filler.id = "switch-version-filler";
+ document.body.appendChild(filler);
+
+ function changeSidebarTop() {
+ const height = switcherEl.getBoundingClientRect().height;
+ const sidebar = document.querySelector(".sidebar");
+ sidebar.style.top = height + 1 + "px";
+ }
+ setTimeout(() => {
+ const sidebar = window.getComputedStyle(document.querySelector(".sidebar"));
+ filler.style.width = sidebar.width;
+ filler.style.backgroundColor = sidebar.backgroundColor;
+ changeSidebarTop();
+ }, 0); // it'll be computed once it's added in the DOM.
+ window.addEventListener("resize", changeSidebarTop);
+}
+
+function setupStyleFor22(rustdoc_container, switcherEl) {
+ // It's mostly the same as `setupStyleFor32` so we call it and make the extra changes afterward.
+ setupStyleFor32(rustdoc_container, switcherEl, `@media (max-width: 700px) {
+ .sidebar {
+ height: 45px;
+ min-height: 40px;
+ margin: 0;
+ margin-left: -15px;
+ padding: 0 15px;
+ position: static;
+ z-index: 1;
+ }
+}`);
+}
+
+function setupStyleFor21(rustdoc_container, switcherEl) {
+ // It's mostly the same as `setupStyleFor22` so we call it and make the extra changes afterward.
+ document.body.style.padding = "0";
+
+ const css = `.rustdoc {
+ padding: 10px 15px 20px 15px !important;
+}`;
+ addStyle(css);
+
+ function changeSidebarTop() {
+ const height = switcherEl.getBoundingClientRect().height;
+ const sidebar = document.querySelector(".sidebar");
+ sidebar.style.top = height + 1 + "px";
+ }
+ setTimeout(() => {
+ changeSidebarTop();
+ }, 0); // it'll be computed once it's added in the DOM.
+ window.addEventListener("resize", changeSidebarTop);
+}
+
+function getHtmlForSwitcher(isOldVersion, switcher_container) {
+ if (!isOldVersion) {
+ switcher_container.style.color = "#eee";
+ return "You can pick a different version with this dropdown: ";
+ }
+
+ switcher_container.style.color = "#e57300";
+
+ addStyle(`#doc-version-switcher svg {
+ width: 1em;
+ height: 1em;
+ fill: currentColor;
+ padding-top: 0.1em;
+}`);
+
+ const warning_img = ` `;
+ return warning_img + "You are seeing an outdated version of this documentation. " +
+ "Click on the dropdown to go to the latest stable version: ";
+}
+
+function showSwitcher(isOldVersion) {
+ const el = document.createElement("div");
+
+ el.style.borderBottom = "1px solid #bbb";
+ el.style.fontSize = "1.1em";
+ el.style.padding = "4px";
+ el.style.background = "#111";
+ el.style.width = "100%";
+ el.id = "doc-version-switcher";
+
+ const parts = window.location.pathname.split("/");
+ parts[1] = "stable";
+ const url = parts.join("/");
+
+ const current_doc_version = window.location.pathname.split("/")[1];
+ const version_picker = document.createElement("select");
+
+ version_picker.appendChild(createOption("nightly", current_doc_version === "nightly"));
+ version_picker.appendChild(createOption("beta", current_doc_version === "beta"));
+ version_picker.appendChild(createOption("stable", current_doc_version === "stable"));
+
+ for (let medium = get_current_version(); medium >= 0; --medium) {
+ const version = `1.${medium}.0`;
+ version_picker.appendChild(createOption(version, version === current_doc_version));
+ }
+
+ version_picker.style.color = "#000";
+ version_picker.onchange = (event) => {
+ const url_parts = window.location.pathname.split("/");
+ url_parts[1] = event.target.value;
+ window.location.href = url_parts.join("/");
+ };
+
+ const span = document.createElement("span");
+ span.innerHTML = getHtmlForSwitcher(isOldVersion, el);
+ span.appendChild(version_picker);
+
+ el.appendChild(span);
+
+ const rustdoc_container = document.createElement("div");
+
+ let medium_version = current_doc_version.split(".").slice(1, 2);
+ if (medium_version.length === 0) {
+ medium_version = ["-1"];
+ }
+ medium_version = parseInt(medium_version[0]);
+ if (medium_version < 0 || medium_version > 59) {
+ setupStyleFor60(rustdoc_container, el);
+ } else if (medium_version > 58) {
+ setupStyleFor59(rustdoc_container, el);
+ } else if (medium_version > 31) {
+ setupStyleFor32(rustdoc_container, el, "");
+ } else if (medium_version > 21) {
+ setupStyleFor22(rustdoc_container, el);
+ } else {
+ setupStyleFor21(rustdoc_container, el);
+ }
+
+ rustdoc_container.className = document.body.className;
+ document.body.className = "";
+ while (document.body.childNodes.length > 0) {
+ rustdoc_container.appendChild(document.body.childNodes[0]);
+ }
+
+ document.body.appendChild(el);
+ document.body.appendChild(rustdoc_container);
+}
+
+showSwitcher(checkIfIsOldVersion());
+
+}());