Skip to content

Unify keyboard events on docs.rs results #1452

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

Merged
merged 1 commit into from
Sep 12, 2021
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
107 changes: 71 additions & 36 deletions static/keyboard.js
Original file line number Diff line number Diff line change
@@ -1,66 +1,101 @@
(function() {
function focusSearchInput() {
// On the index page, we have a "#search" input. If we are on this page, we want to go back
// to this one and not the one in the header navbar.
var searchInput = document.getElementById("search");
if (searchInput) {
searchInput.focus();
} else {
document.getElementById("nav-search").focus()
}
}

function focusFirstSearchResult() {
var elem = document.querySelector(".recent-releases-container a.release");
if (elem) {
elem.focus();
}
}

function getWrappingLi(elem) {
while (elem.tagName !== "LI") {
elem = elem.parentElement;
}
return elem;
}

function focusOnLi(li) {
var elem = li.querySelector(".release");
if (elem) {
elem.focus();
}
}

function getKey(ev) {
if ("key" in ev && typeof ev.key != "undefined") {
return ev.key;
}
return String.fromCharCode(ev.charCode || ev.keyCode);
}

var active = null;
function checkIfHasParent(elem, className) {
while (elem && elem.tagName !== "BODY") {
elem = elem.parentElement;
if (elem.classList.constains(className)) {
return true;
}
}
return false;
}

function handleKey(ev) {
if (ev.ctrlKey || ev.altKey || ev.metaKey || document.activeElement.tagName === "INPUT") {
if (ev.ctrlKey || ev.altKey || ev.metaKey) {
return;
}
var tagName = document.activeElement.tagName;
if (["BODY", "INPUT"].indexOf(tagName) === -1 &&
tagName !== "A" &&
!checkIfHasParent(document.activeElement, "recent-releases-container"))
{
return;
}

if (ev.which === 40) { // Down arrow
ev.preventDefault();
if (active === null) {
active = document.getElementsByClassName("recent-releases-container")[0].getElementsByTagName("li")[0];
} else if (active.nextElementSibling) {
active.classList.remove("selected");
active = active.nextElementSibling;
if (tagName === "BODY") {
focusFirstSearchResult();
} else {
var wrappingLi = getWrappingLi(document.activeElement);
if (wrappingLi.nextElementSibling) {
focusOnLi(wrappingLi.nextElementSibling);
}
}
active.classList.add("selected");
} else if (ev.which === 38) { // Up arrow
ev.preventDefault();
if (active === null) {
active = document.getElementsByClassName("recent-releases-container")[0].getElementsByTagName("li")[0];
} else if (active.previousElementSibling) {
active.classList.remove("selected");
active = active.previousElementSibling;
}
active.classList.add("selected");
active.focus();
} else if (ev.which === 13) { // Return
if (active !== null) {
document.location.href = active.getElementsByTagName("a")[0].href;
if (tagName === "A") {
var wrappingLi = getWrappingLi(document.activeElement);
if (wrappingLi.previousElementSibling)
{
focusOnLi(wrappingLi.previousElementSibling);
} else {
focusSearchInput();
}
} else if (tagName === "BODY") {
focusFirstSearchResult();
}
} else {
} else if (ev.which === 27) { // Escape
document.activeElement.blur();
} else if (tagName !== "INPUT") {
switch (getKey(ev)) {
case "s":
case "S":
ev.preventDefault();
var searchInputNav = document.getElementsByClassName("search-input-nav");
if (searchInputNav.length > 0) {
searchInputNav[0].focus();
}
focusSearchInput();
break;
}
}
}

document.onkeypress = handleKey;
document.onkeydown = handleKey;

var crates = Array.prototype.slice.call(document.getElementsByClassName("recent-releases-container")[0].getElementsByTagName("li"));
for (var i = 0; i < crates.length; ++i) {
crates[i].addEventListener("mouseover", function (event) {
this.classList.remove("selected");
active = null;
});
crates[i].addEventListener("mouseout", function (event) {
this.classList.remove("selected");
active = null;
});
}
})();
27 changes: 1 addition & 26 deletions templates/core/home.html
Original file line number Diff line number Diff line change
Expand Up @@ -72,30 +72,5 @@ <h1 class="brand">{{ "cubes" | fas(fw=true) }} Docs.rs</h1>
{%- endblock body -%}

{%- block javascript -%}
<script type="text/javascript" nonce="{{ csp_nonce }}">
function getKey(ev) {
if ("key" in ev && typeof ev.key != "undefined") {
return ev.key;
}

return String.fromCharCode(ev.charCode || ev.keyCode);
}

function handleShortcut(ev) {
if (ev.ctrlKey || ev.altKey || ev.metaKey || document.activeElement.tagName === "INPUT") {
return;
}

switch (getKey(ev)) {
case "s":
case "S":
ev.preventDefault();
document.getElementById("search").focus();
break;
}
}

document.onkeypress = handleShortcut;
document.onkeydown = handleShortcut;
</script>
<script nonce="{{ csp_nonce }}" type="text/javascript" src="/-/static/keyboard.js?{{ docsrs_version() | slugify }}"></script>
{%- endblock javascript -%}
4 changes: 2 additions & 2 deletions templates/style/base.scss
Original file line number Diff line number Diff line change
Expand Up @@ -272,11 +272,11 @@ div.recent-releases-container {
}

.release:hover,
li.selected > .release {
a.release:focus {
background-color: var(--color-background-code);
}

li:last-child .release {
li:last-of-type .release {
border-bottom: none;
}

Expand Down