Skip to content

Instantly share code, notes, and snippets.

@harsilspatel
Last active May 28, 2023 11:58
Show Gist options
  • Save harsilspatel/886c37b5a4c0c7de97a2bf1983bd8f7a to your computer and use it in GitHub Desktop.
Save harsilspatel/886c37b5a4c0c7de97a2bf1983bd8f7a to your computer and use it in GitHub Desktop.
A Userscript to focus search bar on "/" keypress
// ==UserScript==
// @name forward-slash
// @namespace harsilspatel
// @version 0.7
// @description press "/" to focus on search bar
// @author harsilspatel
// @match *://*/*
// @downloadURL https://gist.github.com/harsilspatel/886c37b5a4c0c7de97a2bf1983bd8f7a/raw/forward-slash.user.js
// @updateURL https://gist.github.com/harsilspatel/886c37b5a4c0c7de97a2bf1983bd8f7a/raw/forward-slash.user.js
// @icon https://www.google.com/s2/favicons?domain=google.com
// @grant none
// ==/UserScript==
(function () {
"use strict";
// https://stackoverflow.com/a/38795917/9701238
const isTextBox = (element) => {
console.log("isTextBox", element);
const tagName = (element.tagName || "").toLowerCase();
if (tagName === "textarea") return true;
const type = (element.getAttribute("type") || "").toLowerCase(),
// if any of these input types is not supported by a browser, it will behave as input type text.
inputTypes = ["text", "password", "number", "email", "tel", "url", "search", "date", "datetime", "datetime-local", "time", "month", "week"];
if (inputTypes.indexOf(type) >= 0) return true;
console.log("element.contentEditable", element.contentEditable === "true", element.getAttribute("role") === "textbox");
return element.contentEditable === "true" || element.getAttribute("role") === "textbox";
};
const isVisible = (element) => !(element.offsetParent === null);
const focusInput = (node) => {
node.focus();
const length = node.value.length;
node.setSelectionRange(length, length);
return;
};
// source: https://stackoverflow.com/a/7557433/9701238
const isElementInViewport = (el) => {
const rect = el.getBoundingClientRect();
return (
rect.top >= 0 &&
rect.left >= 0 &&
rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) /* or $(window).height() */ &&
rect.right <= (window.innerWidth || document.documentElement.clientWidth) /* or $(window).width() */
);
};
const nonTextInputFields = ["button", "checkbox", "file", "image", "radio", "reset", "submit"];
const querySelectorAllWrapper = (getOnlyViewportElements) => (selector) =>
Array.from(window.document.querySelectorAll(selector))
.filter(isVisible)
.filter((el) => (getOnlyViewportElements ? isElementInViewport(el) : true))
.filter((el) => !nonTextInputFields.includes(el.type)); // we need to filter out buttons and shit
const keydownListener = (e) => {
// return if user is typing
if (e.code !== "Slash" || isTextBox(e.target)) return;
e.preventDefault();
// sometimes there are multiple search bars (some of them not in viewport)
// by default i want the ones in the viewport
// but if i press option + / then it should get me the first search bar
const isOptionPressed = e.altKey;
const querySelectorAll = querySelectorAllWrapper(!isOptionPressed);
const searchInputs = querySelectorAll("[type='search']");
console.log("input[type='search']", searchInputs);
if (searchInputs.length === 1) return focusInput(searchInputs[0]);
const qInputs = querySelectorAll("[name='q']");
console.log("input[name='q']", qInputs);
if (qInputs.length === 1) return focusInput(qInputs[0]);
const textInputs = querySelectorAll("input[type='text']");
console.log("input[type='text']", textInputs);
if (textInputs.length === 1) return focusInput(textInputs[0]);
const filteredTextInputs = textInputs.filter((element) => (element.ariaLabel || "").toLowerCase() === "search");
console.log("filteredTextInputs", filteredTextInputs);
if (filteredTextInputs.length === 1) return focusInput(filteredTextInputs[0]);
const inputWithSearchPlaceholder = searchInputs.concat(qInputs, textInputs).find((input) => (input.placeholder || "").toLowerCase().includes("search"));
console.log("inputWithSearchPlaceholder", inputWithSearchPlaceholder);
if (inputWithSearchPlaceholder) return focusInput(inputWithSearchPlaceholder);
const inputs = querySelectorAll("input");
console.log("input", inputs);
if (inputs.length === 1) return focusInput(inputs[0]);
};
console.log("forward-slash script loaded");
window.addEventListener("keypress", keydownListener);
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment