Skip to content
Merged
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
156 changes: 156 additions & 0 deletions mkdocs_doubleslash_theme/assets/javascripts/content-width-toggle.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
(function () {
"use strict";

var STORAGE_KEY = "ds-content-wide";
var DESKTOP_QUERY = "(min-width: 76.25em)";
var LABEL_COLLAPSE = "Navigation ausblenden";
var LABEL_EXPAND = "Navigation einblenden";

function isWideMode() {
return document.documentElement.hasAttribute("data-ds-content-wide");
}

function setWideMode(enabled) {
if (enabled) {
document.documentElement.setAttribute("data-ds-content-wide", "");
try {
localStorage.setItem(STORAGE_KEY, "true");
} catch (e) {}
} else {
document.documentElement.removeAttribute("data-ds-content-wide");
try {
localStorage.removeItem(STORAGE_KEY);
} catch (e) {}
}
}

function sidebarIsHidden(sidebar) {
return !sidebar || sidebar.hasAttribute("hidden");
}

function hasCollapsibleSidebars() {
var primary = document.querySelector(".md-sidebar--primary");
return !sidebarIsHidden(primary);
}

function updateButtonState(button) {
var wide = isWideMode();
var collapseIcon = button.querySelector(".ds-content-width-toggle__icon--collapse");
var expandIcon = button.querySelector(".ds-content-width-toggle__icon--expand");

button.setAttribute("aria-pressed", wide ? "true" : "false");
button.setAttribute("aria-label", wide ? LABEL_EXPAND : LABEL_COLLAPSE);
button.setAttribute("title", wide ? LABEL_EXPAND : LABEL_COLLAPSE);

if (collapseIcon) {
collapseIcon.hidden = wide;
}
if (expandIcon) {
expandIcon.hidden = !wide;
}
}

function updateButtonVisibility(button, desktopMedia) {
var shouldShow = desktopMedia.matches && hasCollapsibleSidebars();
button.hidden = !shouldShow;

if (!shouldShow && isWideMode()) {
setWideMode(false);
updateButtonState(button);
document.dispatchEvent(new CustomEvent("ds-content-wide-change"));
}
}

function updateButtonPosition(button) {
if (isWideMode() || button.hidden) {
button.style.removeProperty("left");
return;
}

var content = document.querySelector(".md-content");
if (!content) {
button.style.removeProperty("left");
return;
}

// Align with .md-content border-left (the divider belongs to content, not the sidebar).
button.style.left = content.getBoundingClientRect().left + "px";
}

function scheduleButtonPositionUpdate(button) {
window.requestAnimationFrame(function () {
updateButtonPosition(button);
});
}

function init() {
var button = document.querySelector("[data-md-component='content-width-toggle']");
if (!button) {
return;
}

var desktopMedia = window.matchMedia(DESKTOP_QUERY);

try {
if (localStorage.getItem(STORAGE_KEY) === "true") {
setWideMode(true);
}
} catch (e) {}

function refreshButton(button, desktopMedia) {
updateButtonVisibility(button, desktopMedia);
updateButtonState(button);
updateButtonPosition(button);
}

refreshButton(button, desktopMedia);

button.addEventListener("click", function () {
setWideMode(!isWideMode());
refreshButton(button, desktopMedia);
document.dispatchEvent(new CustomEvent("ds-content-wide-change"));
});

desktopMedia.addEventListener("change", function () {
refreshButton(button, desktopMedia);
});

window.addEventListener("resize", function () {
refreshButton(button, desktopMedia);
});

document.addEventListener("ds-toc-collapse-change", function () {
scheduleButtonPositionUpdate(button);
});

if (typeof MutationObserver !== "undefined") {
var layoutObserver = new MutationObserver(function () {
scheduleButtonPositionUpdate(button);
});
layoutObserver.observe(document.documentElement, {
attributes: true,
attributeFilter: ["data-ds-toc-collapsed", "data-ds-content-wide"],
});
}

if (typeof ResizeObserver !== "undefined") {
var resizeObserver = new ResizeObserver(function () {
scheduleButtonPositionUpdate(button);
});
var content = document.querySelector(".md-content");
var mainInner = document.querySelector(".md-main__inner");
if (content) {
resizeObserver.observe(content);
}
if (mainInner) {
resizeObserver.observe(mainInner);
}
}
}

if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", init);
} else {
init();
}
})();
227 changes: 227 additions & 0 deletions mkdocs_doubleslash_theme/assets/javascripts/toc-collapse-toggle.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,227 @@
(function () {
"use strict";

var STORAGE_KEY = "ds-toc-collapsed";
var TOOLTIP_STORAGE_KEY = "ds-toc-toggle-tooltip-shown";
var DESKTOP_QUERY = "(min-width: 76.25em)";
var TOOLTIP_DURATION_MS = 3000;
var LABEL_COLLAPSE = "Inhaltsverzeichnis ausblenden";
var LABEL_EXPAND = "Inhaltsverzeichnis einblenden";

function isTocCollapsed() {
return document.documentElement.hasAttribute("data-ds-toc-collapsed");
}

function setTocCollapsed(enabled) {
if (enabled) {
document.documentElement.setAttribute("data-ds-toc-collapsed", "");
try {
localStorage.setItem(STORAGE_KEY, "true");
} catch (e) {}
} else {
document.documentElement.removeAttribute("data-ds-toc-collapsed");
try {
localStorage.removeItem(STORAGE_KEY);
} catch (e) {}
}
}

function tocIsUnavailable() {
var secondary = document.querySelector(".md-sidebar--secondary");
if (!secondary || secondary.hasAttribute("hidden")) {
return true;
}
// Material hides the secondary sidebar on small screens (display: none),
// where the ToC is folded into the navigation drawer instead. offsetParent
// is null when the element (or an ancestor) is display:none, so this covers
// the responsive case without hardcoding a breakpoint. A collapsed sidebar
// uses visibility:hidden (not display:none), so it still keeps a layout box
// and the button stays available to expand it again.
if (secondary.offsetParent === null) {
return true;
}
// Material renders the secondary sidebar even for pages without headings,
// leaving an empty ToC. Treat an empty ToC as hidden so the toggle button
// is only shown when there is an actual table of contents to collapse.
return !secondary.querySelector(".md-nav--secondary .md-nav__item");
}

function updateButtonState(button) {
var collapsed = isTocCollapsed();
var collapseIcon = button.querySelector(".ds-toc-toggle__icon--collapse");
var expandIcon = button.querySelector(".ds-toc-toggle__icon--expand");

button.setAttribute("aria-pressed", collapsed ? "true" : "false");
button.setAttribute("aria-label", collapsed ? LABEL_EXPAND : LABEL_COLLAPSE);
button.setAttribute("title", collapsed ? LABEL_EXPAND : LABEL_COLLAPSE);

if (collapseIcon) {
collapseIcon.hidden = collapsed;
}
if (expandIcon) {
expandIcon.hidden = !collapsed;
}
}

function shouldKeepTooltipVisible(button, isHovered) {
return document.activeElement === button || isHovered;
}

function showTooltip(tooltip) {
if (!tooltip) {
return;
}
tooltip.classList.add("ds-toc-toggle__tooltip--visible");
}

function hideTooltip(tooltip, button, isHovered, force) {
if (!tooltip || !tooltip.classList.contains("ds-toc-toggle__tooltip--visible")) {
return;
}
if (!force && button && shouldKeepTooltipVisible(button, isHovered)) {
return;
}
tooltip.classList.remove("ds-toc-toggle__tooltip--visible");
}

function bindTooltipInteractions(button, tooltip) {
var isHovered = false;
var autoHideTimer = null;

function clearAutoHideTimer() {
if (autoHideTimer !== null) {
window.clearTimeout(autoHideTimer);
autoHideTimer = null;
}
}

function hideIfUnpinned(force) {
hideTooltip(tooltip, button, isHovered, force);
}

function scheduleAutoHide() {
clearAutoHideTimer();
autoHideTimer = window.setTimeout(function () {
hideIfUnpinned(false);
}, TOOLTIP_DURATION_MS);
}

button.addEventListener("focus", function () {
clearAutoHideTimer();
showTooltip(tooltip);
});

button.addEventListener("blur", function () {
hideIfUnpinned(false);
});

button.addEventListener("mouseenter", function () {
isHovered = true;
clearAutoHideTimer();
showTooltip(tooltip);
});

button.addEventListener("mouseleave", function () {
isHovered = false;
hideIfUnpinned(false);
});

return {
scheduleAutoHide: scheduleAutoHide,
clearAutoHideTimer: clearAutoHideTimer,
hideIfUnpinned: hideIfUnpinned,
};
}

function updateButtonVisibility(button, tooltip, tooltipControl) {
var wasVisible = !button.hidden;
var shouldShow = !tocIsUnavailable();
button.hidden = !shouldShow;

if (!shouldShow) {
if (wasVisible) {
tooltipControl.clearAutoHideTimer();
tooltipControl.hideIfUnpinned(true);
}
if (isTocCollapsed()) {
setTocCollapsed(false);
updateButtonState(button);
}
return;
}

maybeShowInitialTooltip(button, tooltip, tooltipControl);
}

function maybeShowInitialTooltip(button, tooltip, tooltipControl) {
if (!tooltip || button.hidden) {
return;
}

try {
if (sessionStorage.getItem(TOOLTIP_STORAGE_KEY) === "true") {
return;
}
} catch (e) {
return;
}

showInitialTooltip(tooltip, tooltipControl);
}

function showInitialTooltip(tooltip, tooltipControl) {
if (!tooltip || tooltip.classList.contains("ds-toc-toggle__tooltip--visible")) {
return;
}

try {
sessionStorage.setItem(TOOLTIP_STORAGE_KEY, "true");
} catch (e) {}

window.requestAnimationFrame(function () {
showTooltip(tooltip);
tooltipControl.scheduleAutoHide();
});
}

function init() {
var button = document.querySelector("[data-md-component='toc-collapse-toggle']");
var tooltip = document.querySelector("[data-md-component='toc-collapse-tooltip']");
if (!button) {
return;
}

var tooltipControl = bindTooltipInteractions(button, tooltip);
var desktopMedia = window.matchMedia(DESKTOP_QUERY);

try {
if (localStorage.getItem(STORAGE_KEY) === "true") {
setTocCollapsed(true);
}
} catch (e) {}

updateButtonState(button);
updateButtonVisibility(button, tooltip, tooltipControl);

button.addEventListener("click", function () {
setTocCollapsed(!isTocCollapsed());
updateButtonState(button);
document.dispatchEvent(new CustomEvent("ds-toc-collapse-change"));
});

desktopMedia.addEventListener("change", function () {
updateButtonVisibility(button, tooltip, tooltipControl);
updateButtonState(button);
});

window.addEventListener("resize", function () {
updateButtonVisibility(button, tooltip, tooltipControl);
});
}

if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", init);
} else {
init();
}
})();
Loading
Loading