Add shortkey settings
This commit is contained in:
parent
b5ffc734a2
commit
3f0ee6d925
|
@ -21,7 +21,7 @@ In that situation, the menu icon (three horizontal bars) at the top-left of the
|
|||
|
||||
The **arrow buttons** at the bottom of the page can be used to navigate to the previous or the next chapter.
|
||||
|
||||
The **left and right arrow keys** on the keyboard can be used to navigate to the previous or the next chapter.
|
||||
Pressing <kbd>Ctrl</kbd>+<kbd>←</kbd> and <kbd>Ctrl</kbd>+<kbd>→</kbd> (<kbd>⌘</kbd>+<kbd>←</kbd> and <kbd>⌘</kbd>+<kbd>→</kbd> on Mac) on the keyboard can be used to navigate to the previous or the next chapter.
|
||||
|
||||
## Top menu bar
|
||||
|
||||
|
@ -31,7 +31,7 @@ The icons displayed will depend on the settings of how the book was generated.
|
|||
| Icon | Description |
|
||||
|------|-------------|
|
||||
| <i class="fa fa-bars"></i> | Opens and closes the chapter listing sidebar. |
|
||||
| <i class="fa fa-paint-brush"></i> | Opens a picker to choose a different color theme. |
|
||||
| <i class="fa fa-cog"></i> | Opens a settings menu for setting a different color theme or shortcut keys. |
|
||||
| <i class="fa fa-search"></i> | Opens a search bar for searching within the book. |
|
||||
| <i class="fa fa-print"></i> | Instructs the web browser to print the entire book. |
|
||||
| <i class="fa fa-github"></i> | Opens a link to the website that hosts the source code of the book. |
|
||||
|
|
|
@ -285,9 +285,7 @@ function playground_text(playground, hidden = true) {
|
|||
})();
|
||||
|
||||
(function themes() {
|
||||
var html = document.querySelector('html');
|
||||
var themeToggleButton = document.getElementById('theme-toggle');
|
||||
var themePopup = document.getElementById('theme-list');
|
||||
var html = document.documentElement;
|
||||
var themeColorMetaTag = document.querySelector('meta[name="theme-color"]');
|
||||
var stylesheets = {
|
||||
ayuHighlight: document.querySelector("[href$='ayu-highlight.css']"),
|
||||
|
@ -295,30 +293,11 @@ function playground_text(playground, hidden = true) {
|
|||
highlight: document.querySelector("[href$='highlight.css']"),
|
||||
};
|
||||
|
||||
function showThemes() {
|
||||
themePopup.style.display = 'block';
|
||||
themeToggleButton.setAttribute('aria-expanded', true);
|
||||
themePopup.querySelector("button#" + get_theme()).focus();
|
||||
}
|
||||
|
||||
function updateThemeSelected() {
|
||||
themePopup.querySelectorAll('.theme-selected').forEach(function (el) {
|
||||
el.classList.remove('theme-selected');
|
||||
});
|
||||
themePopup.querySelector("button#" + get_theme()).classList.add('theme-selected');
|
||||
}
|
||||
|
||||
function hideThemes() {
|
||||
themePopup.style.display = 'none';
|
||||
themeToggleButton.setAttribute('aria-expanded', false);
|
||||
themeToggleButton.focus();
|
||||
}
|
||||
|
||||
function get_theme() {
|
||||
var theme;
|
||||
try { theme = localStorage.getItem('mdbook-theme'); } catch (e) { }
|
||||
if (theme === null || theme === undefined) {
|
||||
return default_theme;
|
||||
return window.default_theme;
|
||||
} else {
|
||||
return theme;
|
||||
}
|
||||
|
@ -363,81 +342,11 @@ function playground_text(playground, hidden = true) {
|
|||
|
||||
html.classList.remove(previousTheme);
|
||||
html.classList.add(theme);
|
||||
updateThemeSelected();
|
||||
}
|
||||
|
||||
// Set theme
|
||||
var theme = get_theme();
|
||||
set_theme(get_theme(), false);
|
||||
|
||||
set_theme(theme, false);
|
||||
|
||||
themeToggleButton.addEventListener('click', function () {
|
||||
if (themePopup.style.display === 'block') {
|
||||
hideThemes();
|
||||
} else {
|
||||
showThemes();
|
||||
}
|
||||
});
|
||||
|
||||
themePopup.addEventListener('click', function (e) {
|
||||
var theme;
|
||||
if (e.target.className === "theme") {
|
||||
theme = e.target.id;
|
||||
} else if (e.target.parentElement.className === "theme") {
|
||||
theme = e.target.parentElement.id;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
set_theme(theme);
|
||||
});
|
||||
|
||||
themePopup.addEventListener('focusout', function(e) {
|
||||
// e.relatedTarget is null in Safari and Firefox on macOS (see workaround below)
|
||||
if (!!e.relatedTarget && !themeToggleButton.contains(e.relatedTarget) && !themePopup.contains(e.relatedTarget)) {
|
||||
hideThemes();
|
||||
}
|
||||
});
|
||||
|
||||
// Should not be needed, but it works around an issue on macOS & iOS: https://github.com/rust-lang/mdBook/issues/628
|
||||
document.addEventListener('click', function(e) {
|
||||
if (themePopup.style.display === 'block' && !themeToggleButton.contains(e.target) && !themePopup.contains(e.target)) {
|
||||
hideThemes();
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener('keydown', function (e) {
|
||||
if (e.altKey || e.ctrlKey || e.metaKey || e.shiftKey) { return; }
|
||||
if (!themePopup.contains(e.target)) { return; }
|
||||
|
||||
switch (e.key) {
|
||||
case 'Escape':
|
||||
e.preventDefault();
|
||||
hideThemes();
|
||||
break;
|
||||
case 'ArrowUp':
|
||||
e.preventDefault();
|
||||
var li = document.activeElement.parentElement;
|
||||
if (li && li.previousElementSibling) {
|
||||
li.previousElementSibling.querySelector('button').focus();
|
||||
}
|
||||
break;
|
||||
case 'ArrowDown':
|
||||
e.preventDefault();
|
||||
var li = document.activeElement.parentElement;
|
||||
if (li && li.nextElementSibling) {
|
||||
li.nextElementSibling.querySelector('button').focus();
|
||||
}
|
||||
break;
|
||||
case 'Home':
|
||||
e.preventDefault();
|
||||
themePopup.querySelector('li:first-child button').focus();
|
||||
break;
|
||||
case 'End':
|
||||
e.preventDefault();
|
||||
themePopup.querySelector('li:last-child button').focus();
|
||||
break;
|
||||
}
|
||||
});
|
||||
window.set_theme = set_theme;
|
||||
})();
|
||||
|
||||
(function sidebar() {
|
||||
|
@ -560,30 +469,6 @@ function playground_text(playground, hidden = true) {
|
|||
}
|
||||
})();
|
||||
|
||||
(function chapterNavigation() {
|
||||
document.addEventListener('keydown', function (e) {
|
||||
if (e.altKey || e.ctrlKey || e.metaKey || e.shiftKey) { return; }
|
||||
if (window.search && window.search.hasFocus()) { return; }
|
||||
|
||||
switch (e.key) {
|
||||
case 'ArrowRight':
|
||||
e.preventDefault();
|
||||
var nextButton = document.querySelector('.nav-chapters.next');
|
||||
if (nextButton) {
|
||||
window.location.href = nextButton.href;
|
||||
}
|
||||
break;
|
||||
case 'ArrowLeft':
|
||||
e.preventDefault();
|
||||
var previousButton = document.querySelector('.nav-chapters.previous');
|
||||
if (previousButton) {
|
||||
window.location.href = previousButton.href;
|
||||
}
|
||||
break;
|
||||
}
|
||||
});
|
||||
})();
|
||||
|
||||
(function clipboard() {
|
||||
var clipButtons = document.querySelectorAll('.clip-button');
|
||||
|
||||
|
@ -686,3 +571,311 @@ function playground_text(playground, hidden = true) {
|
|||
}, { passive: true });
|
||||
})();
|
||||
})();
|
||||
|
||||
(function settings() {
|
||||
const toggle = document.querySelector("#settings-toggle");
|
||||
const menu = document.querySelector("#settings-menu");
|
||||
|
||||
const isMac = /^Mac/i.test(navigator.userAgentData?.platform ?? navigator.platform);
|
||||
const isTouchDevice = window.matchMedia("(pointer: coarse)").matches;
|
||||
|
||||
const eventModifiers = Object.fromEntries(["ctrl", "alt", "shift", "meta"]
|
||||
.map((k) => [k, `${k}Key`]));
|
||||
|
||||
const defaultComboModifier = isMac ? "Meta" : "Control";
|
||||
|
||||
const defaultShortkeys = [
|
||||
{
|
||||
id: "toc",
|
||||
name: "Toggle Table of Contents",
|
||||
combo: "t",
|
||||
selector: "#sidebar-toggle",
|
||||
},
|
||||
{
|
||||
id: "settings",
|
||||
name: "Open settings",
|
||||
combo: "/",
|
||||
selector: "#settings-toggle",
|
||||
},
|
||||
{
|
||||
id: "search",
|
||||
name: "Search",
|
||||
combo: "s",
|
||||
selector: "#search-toggle",
|
||||
},
|
||||
{
|
||||
id: "previous",
|
||||
name: "Previous chapter",
|
||||
combo: `${defaultComboModifier}+ArrowLeft`,
|
||||
selector: ".nav-chapters.previous",
|
||||
altSelectors: [".mobile-nav-chapters.previous"],
|
||||
},
|
||||
{
|
||||
id: "next",
|
||||
name: "Next chapter",
|
||||
combo: `${defaultComboModifier}+ArrowRight`,
|
||||
selector: ".nav-chapters.next",
|
||||
altSelectors: [".mobile-nav-chapters.next"],
|
||||
},
|
||||
];
|
||||
|
||||
function getCombo(storageKey) {
|
||||
const shortkey = localStorage.getItem(`mdbook-shortkeys::${storageKey}`);
|
||||
return shortkey ?? defaultShortkeys.find((x) => x.id === storageKey).combo;
|
||||
}
|
||||
|
||||
function setCombo(storageKey, combo) {
|
||||
localStorage.setItem(`mdbook-shortkeys::${storageKey}`, combo);
|
||||
}
|
||||
|
||||
function checkIsTextInputMode() {
|
||||
return document.activeElement.isContentEditable ||
|
||||
["INPUT", "TEXTAREA"].includes(document.activeElement.nodeName);
|
||||
}
|
||||
|
||||
function eventToCombo(e) {
|
||||
const normalized = new Map([
|
||||
[" ", "Space"],
|
||||
["+", "Plus"],
|
||||
["Ctrl", "Control"],
|
||||
]);
|
||||
|
||||
const modifierKeys = Object.keys(eventModifiers)
|
||||
.filter((k) => e[eventModifiers[k]])
|
||||
.map((x) => x.charAt(0).toUpperCase() + x.slice(1));
|
||||
|
||||
if (["Control", "Alt", "Shift", "Meta"].includes(e.key)) return null;
|
||||
|
||||
return [...modifierKeys, e.key]
|
||||
.map((x) => normalized.has(x) ? normalized.get(x) : x).join("+");
|
||||
}
|
||||
|
||||
function eventMatchesCombo(e, combo) {
|
||||
const eventCombo = eventToCombo(e);
|
||||
return eventCombo && (eventCombo === combo);
|
||||
}
|
||||
|
||||
function keyToPretty(key) {
|
||||
const fmtMap = new Map([
|
||||
["ArrowRight", "→"],
|
||||
["ArrowLeft", "←"],
|
||||
["ArrowUp", "↑"],
|
||||
["ArrowDown", "↓"],
|
||||
["Plus", "+"],
|
||||
["Control", isMac ? "Control" : "Ctrl"],
|
||||
["Alt", isMac ? "⌥" : "Alt"],
|
||||
["Meta", isMac ? "⌘" : "Meta"],
|
||||
]);
|
||||
|
||||
return fmtMap.has(key) ? fmtMap.get(key) : key;
|
||||
}
|
||||
|
||||
function comboToPretty(combo) {
|
||||
return combo.split("+").map(keyToPretty).join("+");
|
||||
}
|
||||
|
||||
function comboToPrettyHtml(combo) {
|
||||
const html = (text) =>
|
||||
Object.assign(document.createElement("span"), { textContent: text })
|
||||
.innerHTML;
|
||||
|
||||
return combo.split("+").map((x) => `<kbd>${html(keyToPretty(x))}</kbd>`).join(
|
||||
"<span>+</span>",
|
||||
);
|
||||
}
|
||||
|
||||
function renderShortkeyField(shortkey) {
|
||||
const div = document.createElement("div");
|
||||
|
||||
const combo = getCombo(shortkey.id);
|
||||
const touched = combo !== shortkey.combo;
|
||||
const changeLabel = `Change shortcut key for ${shortkey.name}`;
|
||||
const buttonAttrs = (label) =>
|
||||
`aria-label="${label}" title="${label}" aria-controls="shortkey-${shortkey.id}"`;
|
||||
|
||||
div.classList.add("shortkey");
|
||||
div.innerHTML = `<label for="shortkey-${shortkey.id}">${shortkey.name}</label>
|
||||
<div class="shortkey__control${
|
||||
touched ? " shortkey__control--touched" : ""
|
||||
}" data-shortkey-item="${shortkey.id}">
|
||||
<span class="shortkey__input">
|
||||
<input${
|
||||
touched ? "" : " disabled"
|
||||
} id="shortkey-${shortkey.id}" autocomplete="off" value="Control+ArrowLeft">
|
||||
<span aria-hidden="true" class="shortkey__display">${
|
||||
comboToPrettyHtml(combo)
|
||||
}</span>
|
||||
</span>
|
||||
<button class="shortkey__change" type="button" ${buttonAttrs(changeLabel)}>
|
||||
<i class="fa fa-pencil" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>`;
|
||||
|
||||
return div;
|
||||
}
|
||||
|
||||
menu.innerHTML = `
|
||||
<h2>Settings</h2>
|
||||
<fieldset>
|
||||
<legend>
|
||||
<span>Appearance</span>
|
||||
</legend>
|
||||
<div>
|
||||
<label for="theme">Theme</label>
|
||||
<select id="theme">
|
||||
${["light", "rust", "coal", "navy", "ayu"].map((theme) =>
|
||||
`<option${(localStorage.getItem("mdbook-theme") ?? window.default_theme) ===
|
||||
theme
|
||||
? " selected"
|
||||
: ""
|
||||
} value="${theme}">${theme.charAt(0).toUpperCase() + theme.slice(1)}</option>`
|
||||
).join("")
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
</fieldset>
|
||||
${isTouchDevice ? "" : `<fieldset id="shortkeys" class="shortkeys">
|
||||
<legend>
|
||||
<span>Keyboard shortcuts</span>
|
||||
</legend>
|
||||
${defaultShortkeys.map((x) => renderShortkeyField(x).outerHTML).join("")}
|
||||
<div>
|
||||
<button class="shortkeys__reset-all" type="reset">
|
||||
Reset all keyboard shortcuts
|
||||
</button>
|
||||
</div>
|
||||
</fieldset>`}
|
||||
`;
|
||||
|
||||
function updateButtons() {
|
||||
for (const shortkey of defaultShortkeys) {
|
||||
for (
|
||||
const button of document.querySelectorAll(
|
||||
[shortkey.selector, ...(shortkey.altSelectors ?? [])].join(", "),
|
||||
)
|
||||
) {
|
||||
const combo = getCombo(shortkey.id);
|
||||
button.setAttribute("aria-keyshortcuts", combo);
|
||||
button.title = button.title.replace(
|
||||
/(?: \(.+\))?$/,
|
||||
` (${comboToPretty(combo)})`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
updateButtons();
|
||||
|
||||
function toggleSettingsPopup(open = toggle.getAttribute("aria-expanded") !== "true") {
|
||||
toggle.setAttribute("aria-expanded", String(open));
|
||||
menu.hidden = !open;
|
||||
|
||||
if (open) {
|
||||
menu.querySelector("input, button, textarea, select").focus();
|
||||
}
|
||||
}
|
||||
|
||||
toggle.addEventListener("click", () => toggleSettingsPopup());
|
||||
menu.addEventListener("submit", (e) => e.preventDefault());
|
||||
menu.addEventListener("change", updateButtons);
|
||||
|
||||
menu.querySelector("#theme").addEventListener("change", (e) => {
|
||||
window.set_theme(e.target.value);
|
||||
});
|
||||
|
||||
menu.addEventListener("keydown", (e) => {
|
||||
if (!e.target.matches(".shortkey__control input")) return;
|
||||
if (["Escape", "Tab", "Enter", " "].includes(e.key)) return;
|
||||
|
||||
const parent = e.target.closest(".shortkey__control");
|
||||
|
||||
e.preventDefault();
|
||||
const combo = eventToCombo(e);
|
||||
|
||||
if (!combo) return;
|
||||
|
||||
const html = comboToPrettyHtml(combo);
|
||||
|
||||
setCombo(parent.dataset.shortkeyItem, combo);
|
||||
e.target.value = combo;
|
||||
e.currentTarget.dispatchEvent(new Event("change"));
|
||||
|
||||
parent.querySelector(".shortkey__display").innerHTML = html;
|
||||
});
|
||||
|
||||
menu.addEventListener("click", (e) => {
|
||||
if (e.target.closest(".shortkey__control .shortkey__change")) {
|
||||
e.preventDefault();
|
||||
|
||||
const input = e.target.closest(".shortkey__control").querySelector(
|
||||
"input",
|
||||
);
|
||||
input.disabled = false;
|
||||
e.target.closest(".shortkey__control").classList.add(
|
||||
"shortkey__control--touched",
|
||||
);
|
||||
input.focus();
|
||||
} else if (e.target.closest("#shortkeys .shortkeys__reset-all")) {
|
||||
e.preventDefault();
|
||||
|
||||
for (const el of e.currentTarget.querySelectorAll(".shortkey__control")) {
|
||||
const shortkey = defaultShortkeys.find((x) =>
|
||||
x.id === el.dataset.shortkeyItem
|
||||
);
|
||||
|
||||
setCombo(el.dataset.shortkeyItem, shortkey.combo);
|
||||
el.closest(".shortkey").replaceWith(renderShortkeyField(shortkey));
|
||||
}
|
||||
e.currentTarget.dispatchEvent(new Event("change"));
|
||||
}
|
||||
});
|
||||
|
||||
menu.addEventListener("focusout", (e) => {
|
||||
if (e.target.matches(".shortkey__control input")) {
|
||||
e.target.closest(".shortkey").replaceWith(
|
||||
renderShortkeyField(
|
||||
defaultShortkeys.find((x) =>
|
||||
x.id === e.target.closest(".shortkey__control").dataset.shortkeyItem
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener("keydown", (e) => {
|
||||
if (checkIsTextInputMode()) return;
|
||||
|
||||
for (const shortkey of defaultShortkeys) {
|
||||
if (eventMatchesCombo(e, getCombo(shortkey.id))) {
|
||||
e.preventDefault();
|
||||
const button = document.querySelector(shortkey.selector);
|
||||
if (button) {
|
||||
button.focus();
|
||||
button.click();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
window.addEventListener("keydown", (e) => {
|
||||
if (e.key === "Escape") {
|
||||
if (e.target.closest("#settings-menu")) {
|
||||
toggleSettingsPopup(false);
|
||||
toggle.focus();
|
||||
} else if (!checkIsTextInputMode()) {
|
||||
toggleSettingsPopup(false);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
window.addEventListener("click", (e) => {
|
||||
if (
|
||||
e.isTrusted && !e.target.closest("#settings-menu") &&
|
||||
!e.target.closest("#settings-toggle")
|
||||
) {
|
||||
toggleSettingsPopup(false);
|
||||
}
|
||||
});
|
||||
})();
|
||||
|
|
|
@ -87,6 +87,7 @@ a > .hljs {
|
|||
}
|
||||
.right-buttons a {
|
||||
text-decoration: none;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.left-buttons {
|
||||
|
@ -139,8 +140,6 @@ a > .hljs {
|
|||
text-decoration: none;
|
||||
|
||||
position: fixed;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
margin: 0;
|
||||
max-width: 150px;
|
||||
min-width: 90px;
|
||||
|
@ -151,6 +150,10 @@ a > .hljs {
|
|||
flex-direction: column;
|
||||
|
||||
transition: color 0.5s, background-color 0.5s;
|
||||
|
||||
height: 10rem;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
|
||||
.nav-chapters:hover {
|
||||
|
@ -498,48 +501,127 @@ ul#searchresults span.teaser em {
|
|||
line-height: 1.9em;
|
||||
}
|
||||
|
||||
/* Theme Menu Popup */
|
||||
/* Settings menu */
|
||||
|
||||
.theme-popup {
|
||||
#settings-menu {
|
||||
position: absolute;
|
||||
left: 10px;
|
||||
top: var(--menu-bar-height);
|
||||
inset-inline-start: 10px;
|
||||
inset-block-start: var(--menu-bar-height);
|
||||
z-index: 1000;
|
||||
border-radius: 4px;
|
||||
font-size: 0.7em;
|
||||
color: var(--fg);
|
||||
background: var(--theme-popup-bg);
|
||||
border: 1px solid var(--theme-popup-border);
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
display: none;
|
||||
/* Don't let the children's background extend past the rounded corners. */
|
||||
overflow: hidden;
|
||||
width: 32rem;
|
||||
}
|
||||
.theme-popup .default {
|
||||
color: var(--icons);
|
||||
|
||||
#settings-menu * {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.theme-popup .theme {
|
||||
|
||||
#settings-menu h2 {
|
||||
margin: 1rem;
|
||||
}
|
||||
|
||||
#settings-menu fieldset {
|
||||
margin: 1rem;
|
||||
border: 1px solid var(--theme-popup-border);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
#settings-menu select {
|
||||
display: block;
|
||||
padding: 0.6rem;
|
||||
width: 100%;
|
||||
border: 0;
|
||||
margin: 0;
|
||||
padding: 2px 20px;
|
||||
line-height: 25px;
|
||||
white-space: nowrap;
|
||||
text-align: left;
|
||||
cursor: pointer;
|
||||
color: inherit;
|
||||
background: inherit;
|
||||
font-size: inherit;
|
||||
}
|
||||
.theme-popup .theme:hover {
|
||||
|
||||
#settings-menu :is(select, input) {
|
||||
border: 1px solid var(--searchbar-border-color);
|
||||
border-radius: 3px;
|
||||
background-color: var(--searchbar-bg);
|
||||
transition: box-shadow 300ms ease-in-out;
|
||||
color: var(--searchbar-fg);
|
||||
}
|
||||
|
||||
.shortkey__control {
|
||||
position: relative;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
#settings-menu .shortkey__input {
|
||||
flex: 0 1 100%;
|
||||
}
|
||||
|
||||
#settings-menu .shortkey__control input {
|
||||
height: 3.2rem;
|
||||
color: #00000001;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#settings-menu .shortkey__control input:disabled {
|
||||
background: transparent;
|
||||
border: none;
|
||||
}
|
||||
|
||||
#settings-menu .shortkey__control input::selection {
|
||||
color: transparent;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.shortkey__control.shortkey__control--touched .shortkey__change {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.shortkey__control .shortkey__display {
|
||||
position: absolute;
|
||||
inset-inline-start: 0.8rem;
|
||||
inset-block-start: 50%;
|
||||
transform: translateY(-50%);
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
.shortkey__control.shortkey__control--touched .shortkey__display span {
|
||||
color: var(--searchbar-fg);
|
||||
}
|
||||
|
||||
.shortkey__control button {
|
||||
height: 3rem;
|
||||
color: var(--fg);
|
||||
background: none;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.shortkey__control :is(button:hover, button i:hover) {
|
||||
cursor: pointer;
|
||||
color: var(--links);
|
||||
}
|
||||
|
||||
#menu-bar .shortkey__control button i {
|
||||
line-height: initial;
|
||||
}
|
||||
|
||||
#settings-menu .shortkeys__reset-all {
|
||||
cursor: pointer;
|
||||
padding: 0.5rem 0.8rem;
|
||||
font-size: 14px;
|
||||
border-style: solid;
|
||||
border-width: 1px;
|
||||
border-radius: 4px;
|
||||
border-color: var(--icons);
|
||||
background-color: var(--theme-popup-bg);
|
||||
transition: 100ms;
|
||||
transition-property: color,border-color,background-color;
|
||||
color: var(--icons);
|
||||
margin-block-start: 1rem;
|
||||
}
|
||||
|
||||
#settings-menu .shortkeys__reset-all:hover {
|
||||
color: var(--fg);
|
||||
border-color: var(--icons-hover);
|
||||
background-color: var(--theme-hover);
|
||||
}
|
||||
|
||||
.theme-selected::before {
|
||||
display: inline-block;
|
||||
content: "✓";
|
||||
margin-left: -14px;
|
||||
width: 14px;
|
||||
.shortkeys > * {
|
||||
margin-block: 0.3rem;
|
||||
}
|
||||
|
|
|
@ -120,18 +120,12 @@
|
|||
<button id="sidebar-toggle" class="icon-button" type="button" title="Toggle Table of Contents" aria-label="Toggle Table of Contents" aria-controls="sidebar">
|
||||
<i class="fa fa-bars"></i>
|
||||
</button>
|
||||
<button id="theme-toggle" class="icon-button" type="button" title="Change theme" aria-label="Change theme" aria-haspopup="true" aria-expanded="false" aria-controls="theme-list">
|
||||
<i class="fa fa-paint-brush"></i>
|
||||
<button id="settings-toggle" class="icon-button" type="button" title="Settings" aria-label="Settings" aria-haspopup="true" aria-expanded="false" aria-controls="settings-menu">
|
||||
<i class="fa fa-cog"></i>
|
||||
</button>
|
||||
<ul id="theme-list" class="theme-popup" aria-label="Themes" role="menu">
|
||||
<li role="none"><button role="menuitem" class="theme" id="light">Light</button></li>
|
||||
<li role="none"><button role="menuitem" class="theme" id="rust">Rust</button></li>
|
||||
<li role="none"><button role="menuitem" class="theme" id="coal">Coal</button></li>
|
||||
<li role="none"><button role="menuitem" class="theme" id="navy">Navy</button></li>
|
||||
<li role="none"><button role="menuitem" class="theme" id="ayu">Ayu</button></li>
|
||||
</ul>
|
||||
<form hidden id="settings-menu" aria-label="Settings" role="menu"></form>
|
||||
{{#if search_enabled}}
|
||||
<button id="search-toggle" class="icon-button" type="button" title="Search. (Shortkey: s)" aria-label="Toggle Searchbar" aria-expanded="false" aria-keyshortcuts="S" aria-controls="searchbar">
|
||||
<button id="search-toggle" class="icon-button" type="button" title="Search" aria-label="Toggle Searchbar" aria-expanded="false" aria-controls="searchbar">
|
||||
<i class="fa fa-search"></i>
|
||||
</button>
|
||||
{{/if}}
|
||||
|
@ -189,13 +183,13 @@
|
|||
<nav class="nav-wrapper" aria-label="Page navigation">
|
||||
<!-- Mobile navigation buttons -->
|
||||
{{#previous}}
|
||||
<a rel="prev" href="{{ path_to_root }}{{link}}" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
|
||||
<a rel="prev" href="{{ path_to_root }}{{link}}" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter">
|
||||
<i class="fa fa-angle-left"></i>
|
||||
</a>
|
||||
{{/previous}}
|
||||
|
||||
{{#next}}
|
||||
<a rel="next" href="{{ path_to_root }}{{link}}" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
|
||||
<a rel="next" href="{{ path_to_root }}{{link}}" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter">
|
||||
<i class="fa fa-angle-right"></i>
|
||||
</a>
|
||||
{{/next}}
|
||||
|
@ -207,13 +201,13 @@
|
|||
|
||||
<nav class="nav-wide-wrapper" aria-label="Page navigation">
|
||||
{{#previous}}
|
||||
<a rel="prev" href="{{ path_to_root }}{{link}}" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
|
||||
<a rel="prev" href="{{ path_to_root }}{{link}}" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter">
|
||||
<i class="fa fa-angle-left"></i>
|
||||
</a>
|
||||
{{/previous}}
|
||||
|
||||
{{#next}}
|
||||
<a rel="next" href="{{ path_to_root }}{{link}}" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
|
||||
<a rel="next" href="{{ path_to_root }}{{link}}" class="nav-chapters next" title="Next chapter" aria-label="Next chapter">
|
||||
<i class="fa fa-angle-right"></i>
|
||||
</a>
|
||||
{{/next}}
|
||||
|
|
|
@ -48,7 +48,6 @@ window.search = window.search || {};
|
|||
URL_MARK_PARAM = 'highlight',
|
||||
teaser_count = 0,
|
||||
|
||||
SEARCH_HOTKEY_KEYCODE = 83,
|
||||
ESCAPE_KEYCODE = 27,
|
||||
DOWN_KEYCODE = 40,
|
||||
UP_KEYCODE = 38,
|
||||
|
@ -89,7 +88,7 @@ window.search = window.search || {};
|
|||
path: a.pathname.replace(/^([^/])/,'/$1')
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
// Helper to recreate a url string from its building blocks.
|
||||
function renderURL(urlobject) {
|
||||
var url = urlobject.protocol + "://" + urlobject.host;
|
||||
|
@ -109,7 +108,7 @@ window.search = window.search || {};
|
|||
}
|
||||
return url;
|
||||
}
|
||||
|
||||
|
||||
// Helper to escape html special chars for displaying the teasers
|
||||
var escapeHTML = (function() {
|
||||
var MAP = {
|
||||
|
@ -124,7 +123,7 @@ window.search = window.search || {};
|
|||
return s.replace(/[&<>'"]/g, repl);
|
||||
};
|
||||
})();
|
||||
|
||||
|
||||
function formatSearchMetric(count, searchterm) {
|
||||
if (count == 1) {
|
||||
return count + " search result for '" + searchterm + "':";
|
||||
|
@ -134,7 +133,7 @@ window.search = window.search || {};
|
|||
return count + " search results for '" + searchterm + "':";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function formatSearchResult(result, searchterms) {
|
||||
var teaser = makeTeaser(escapeHTML(result.doc.body), searchterms);
|
||||
teaser_count++;
|
||||
|
@ -152,10 +151,10 @@ window.search = window.search || {};
|
|||
|
||||
return '<a href="' + path_to_root + url[0] + '?' + URL_MARK_PARAM + '=' + searchterms + '#' + url[1]
|
||||
+ '" aria-details="teaser_' + teaser_count + '">' + result.doc.breadcrumbs + '</a>'
|
||||
+ '<span class="teaser" id="teaser_' + teaser_count + '" aria-label="Search Result Teaser">'
|
||||
+ '<span class="teaser" id="teaser_' + teaser_count + '" aria-label="Search Result Teaser">'
|
||||
+ teaser + '</span>';
|
||||
}
|
||||
|
||||
|
||||
function makeTeaser(body, searchterms) {
|
||||
// The strategy is as follows:
|
||||
// First, assign a value to each word in the document:
|
||||
|
@ -271,7 +270,7 @@ window.search = window.search || {};
|
|||
// If reloaded, do the search or mark again, depending on the current url parameters
|
||||
doSearchOrMarkFromUrl();
|
||||
}
|
||||
|
||||
|
||||
function unfocusSearchbar() {
|
||||
// hacky, but just focusing a div only works once
|
||||
var tmp = document.createElement('input');
|
||||
|
@ -280,7 +279,7 @@ window.search = window.search || {};
|
|||
tmp.focus();
|
||||
tmp.remove();
|
||||
}
|
||||
|
||||
|
||||
// On reload or browser history backwards/forwards events, parse the url and do search or mark
|
||||
function doSearchOrMarkFromUrl() {
|
||||
// Check current URL for search request
|
||||
|
@ -313,7 +312,7 @@ window.search = window.search || {};
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Eventhandler for keyevents on `document`
|
||||
function globalKeyHandler(e) {
|
||||
if (e.altKey || e.ctrlKey || e.metaKey || e.shiftKey || e.target.type === 'textarea' || e.target.type === 'text') { return; }
|
||||
|
@ -325,14 +324,10 @@ window.search = window.search || {};
|
|||
(searchbar.value.trim() !== "") ? "push" : "replace");
|
||||
if (hasFocus()) {
|
||||
unfocusSearchbar();
|
||||
searchicon.focus();
|
||||
}
|
||||
showSearch(false);
|
||||
marker.unmark();
|
||||
} else if (!hasFocus() && e.keyCode === SEARCH_HOTKEY_KEYCODE) {
|
||||
e.preventDefault();
|
||||
showSearch(true);
|
||||
window.scrollTo(0, 0);
|
||||
searchbar.select();
|
||||
} else if (hasFocus() && e.keyCode === DOWN_KEYCODE) {
|
||||
e.preventDefault();
|
||||
unfocusSearchbar();
|
||||
|
@ -363,7 +358,7 @@ window.search = window.search || {};
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function showSearch(yes) {
|
||||
if (yes) {
|
||||
search_wrap.classList.remove('hidden');
|
||||
|
@ -396,7 +391,7 @@ window.search = window.search || {};
|
|||
showSearch(false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Eventhandler for keyevents while the searchbar is focused
|
||||
function searchbarKeyUpHandler() {
|
||||
var searchterm = searchbar.value.trim();
|
||||
|
@ -414,7 +409,7 @@ window.search = window.search || {};
|
|||
// Remove marks
|
||||
marker.unmark();
|
||||
}
|
||||
|
||||
|
||||
// Update current url with ?URL_SEARCH_PARAM= parameter, remove ?URL_MARK_PARAM and #heading-anchor .
|
||||
// `action` can be one of "push", "replace", "push_if_new_search_else_replace"
|
||||
// and replaces or pushes a new browser history item.
|
||||
|
@ -439,7 +434,7 @@ window.search = window.search || {};
|
|||
history.replaceState({}, document.title, renderURL(url));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function doSearch(searchterm) {
|
||||
|
||||
// Don't search the same twice
|
||||
|
@ -470,7 +465,7 @@ window.search = window.search || {};
|
|||
|
||||
fetch(path_to_root + 'searchindex.json')
|
||||
.then(response => response.json())
|
||||
.then(json => init(json))
|
||||
.then(json => init(json))
|
||||
.catch(error => { // Try to load searchindex.js if fetch failed
|
||||
var script = document.createElement('script');
|
||||
script.src = path_to_root + 'searchindex.js';
|
||||
|
|
Loading…
Reference in New Issue