Sep 12, 2022 05:03 AM - edited Jun 29, 2024 08:09 AM
I put together a bunch of hotkeys to make my own life easier, and figured I might as well clean it up and post it in case any one else found them useful!
This all works via a Tampermonkey script, and installation instructions can be found below
Change Log
⌘/Left Alt + ⇧ Left Shift + ' | Add a new field | |
⌘/Left Alt + ⇧ Right Shift | Customize Field modal - Click “Save” | |
⌘/Left Alt + ' | Next field header | |
⌘/Left Alt + ; | Previous field header | |
s | Next field menu option | |
w | Previous field menu option | |
⌘/Left Alt + ↵ Enter | “Click” selected field menu option | |
⌘/Left Alt + ↵ Enter | Open field menu for selected field | |
Escape | Deselect | |
⌘/Left Alt + ⇧ Left Shift + 1 | Select first cell |
Want another hotkey? Tell me about it and I’ll see what I can do!
I’ve tested this mostly on Firefox on a Mac, but have also tested it a bit on:
And so it should be fine, but do let me know if you hit any issues and I’ll do my best to fix it
You can also customize the keys used as well, and instructions on how can be found in the code itself!
// ==UserScript==
// @name Airtable Hotkeys 1
// @description Adds custom hotkeys to Airtable
// @author Adam - The Time Saving Company
// @author adam@thetimesaving.company
// @match https://airtable.com/*
// @grant GM_addStyle
// ==/UserScript==
let osLeftKey
if(navigator.userAgent.includes("Windows")){
osLeftKey = "AltLeft"
}
else{
osLeftKey = "MetaLeft"
}
// CUSTOMIZATION START
// Changes made here will overwrite the default hotkeys and may cause some weirdness
// Feel free to make changes, but do check against the default hotkey list
// We can use this site to find out what the keys' name is: https://www.toptal.com/developers/keycode/for/enter
// Specifically, look for the value of the "event.code" section
// Set the two keys to press in conjunction that will then hit the "Save" button so that we don't have to use the mouse to click it anymore
// IMPORTANT THING TO NOTE: The key "Enter" cannot be used here as it's in use by Airtable
let saveKey1 = osLeftKey
let saveKey2 = "ShiftRight"
// Set the keys to press in conjunction that will then hit the "Add New Field" button
let addFieldKey1 = osLeftKey
let addFieldKey2 = "ShiftLeft"
let addFieldKey3 = "Quote"
// Set the keys to press in conjunction that will select the field header to the left
let fieldHeaderPrev1 = osLeftKey
let fieldHeaderPrev2 = "Semicolon"
// Set the keys to press in conjunction that will select the field header to the right
let fieldHeaderNext1 = osLeftKey
let fieldHeaderNext2 = "Quote"
// Set the keys to press in conjunction that will open the menu of the selected field header i.e. right clicking it
let fieldMenuOpenKey1 = osLeftKey
let fieldMenuOpenKey2 = "Enter"
// Set the keys to press to move up and down the field's menu
let fieldMenuNavigateUp = "KeyW"
let fieldMenuNavigateDown = "KeyS"
// Set the keys to press in conjunction that will click the selected menu item (i.e. "Customize type", "Rename" etc)
let fieldMenuOptionSelectKey1 = osLeftKey
let fieldMenuOptionSelectKey2 = "Enter"
// Set the key to press that will unselect everything
let escapeKey = "Escape"
let selectFirstCellKey1 = osLeftKey // CMD / Windows button
let selectFirstCellKey2 = "ShiftLeft"
let selectFirstCellKey3 = "Digit1"
// CUSTOMIZATION END
GM_addStyle('.column-selected-by-hotkey { border: blue 1px solid }');
GM_addStyle('.columnMenuItem-selected-by-hotkey { background-color: grey !important}');
let keysPressed = {};
let selectedColumnIndex = 0;
document.addEventListener('keydown', (event) => {
console.log(event)
keysPressed[event.code] = true;
switch (true) {
case keysPressed[selectFirstCellKey1] && keysPressed[selectFirstCellKey2] && event.code == selectFirstCellKey3:
selectFirstCell()
break;
case keysPressed[addFieldKey1] && keysPressed[addFieldKey2] && event.code == addFieldKey3:
addField()
break;
case document.getElementsByClassName('column-selected-by-hotkey')[0] && event.code == escapeKey:
deselect()
break;
case keysPressed[saveKey1] && event.code == saveKey2:
saveFieldChanges()
break;
case document.querySelector("[data-tutorial-selector-id='columnMenuItem-delete']") && keysPressed[fieldMenuNavigateUp]:
navigateFieldMenuOptionsUp()
break;
case document.querySelector("[data-tutorial-selector-id='columnMenuItem-delete']") && keysPressed[fieldMenuNavigateDown]:
navigateFieldMenuOptionsDown()
break;
case keysPressed[fieldHeaderPrev1] && event.code == fieldHeaderPrev2:
navigateFieldHeadersLeft()
break;
case keysPressed[fieldHeaderNext1] && event.code == fieldHeaderNext2:
navigateFieldHeadersRight()
break;
case document.querySelector('.column-selected-by-hotkey') && keysPressed[fieldMenuOpenKey1] && event.code == fieldMenuOpenKey2:
rightClickSelectedFieldHeader()
break;
case document.getElementsByClassName('columnMenuItem-selected-by-hotkey')[0] && keysPressed[fieldMenuOptionSelectKey1] && event.code == fieldMenuOptionSelectKey2:
clickSelectedFieldMenuItem()
break;
}
});
document.addEventListener('keyup', (event) => {
if(navigator.userAgent.includes("Windows")){
delete keysPressed[event.code]
}
else{
keysPressed = {}
}
});
function selectFirstCell() {
simulateMouseClick(document.querySelector(".primary.cell.read[data-rowindex='0']"))
clearClass('column-selected-by-hotkey')
}
function addField() {
document.getElementsByClassName('gridHeaderRowAddFieldButton')[0].click()
clearClass('column-selected-by-hotkey')
}
function deselect() {
document.getElementsByClassName('column-selected-by-hotkey')[0].classList.remove('column-selected-by-hotkey')
selectedColumnIndex = 0
}
function saveFieldChanges() {
if (document.querySelector('[aria-label="Field customization"]')) {
for (const a of document.querySelector('[aria-label="Field customization"]').querySelectorAll("span")) {
if (a.innerText.includes("Save") && !a.innerHTML.includes("span")) {
a.parentElement.click()
}
}
for (const a of document.querySelector('[aria-label="Field customization"]').querySelectorAll("div")) {
if (a.innerText.includes("Save") && !a.innerHTML.includes("div")) {
a.click()
}
}
}
else if (document.querySelector('[aria-label="Create field"]')) {
document.querySelector('[aria-label="Create field"]').querySelector('[data-tutorial-selector-id="columnDialogCreateButton"]').click()
}
}
function navigateFieldMenuOptionsUp() {
if (document.getElementsByClassName('columnMenuItem-selected-by-hotkey')[0]) {
setNextMenuItem("up")
}
else {
document.querySelector("[data-tutorial-selector-id='columnMenuItem-delete']").classList.add('columnMenuItem-selected-by-hotkey')
}
}
function navigateFieldMenuOptionsDown() {
if (document.getElementsByClassName('columnMenuItem-selected-by-hotkey')[0]) {
setNextMenuItem("down")
}
else {
document.querySelector("[data-tutorial-selector-id='columnMenuItem-rename']").classList.add('columnMenuItem-selected-by-hotkey')
}
}
function navigateFieldHeadersLeft() {
// Select Field Left
if (document.getElementsByClassName('column-selected-by-hotkey').length === 0) {
document.querySelector('[data-columnindex="' + selectedColumnIndex + '"][data-tutorial-selector-id="gridHeaderCell"]').firstElementChild.classList.add('column-selected-by-hotkey')
}
else {
selectedColumnIndex--
if (document.querySelector('[data-columnindex="' + selectedColumnIndex + '"][data-tutorial-selector-id="gridHeaderCell"]')) {
document.querySelector('[data-columnindex="' + (selectedColumnIndex + 1) + '"][data-tutorial-selector-id="gridHeaderCell"]').firstElementChild.classList.remove('column-selected-by-hotkey')
document.querySelector('[data-columnindex="' + selectedColumnIndex + '"][data-tutorial-selector-id="gridHeaderCell"]').firstElementChild.classList.add('column-selected-by-hotkey')
}
else {
document.querySelector('[data-columnindex="' + (selectedColumnIndex + 1) + '"][data-tutorial-selector-id="gridHeaderCell"]').firstElementChild.classList.remove('column-selected-by-hotkey')
let columnIndex = document.querySelectorAll('[data-tutorial-selector-id="gridHeaderCell"]').length
selectedColumnIndex = columnIndex - 1
document.querySelector('[data-columnindex="' + selectedColumnIndex + '"][data-tutorial-selector-id="gridHeaderCell"]').firstElementChild.classList.add('column-selected-by-hotkey')
}
}
}
function navigateFieldHeadersRight() {
// Select Field Right
if (document.getElementsByClassName('column-selected-by-hotkey').length === 0) {
document.querySelector('[data-columnindex="' + selectedColumnIndex + '"][data-tutorial-selector-id="gridHeaderCell"]').firstElementChild.classList.add('column-selected-by-hotkey')
}
else {
selectedColumnIndex++
if (document.querySelector('[data-columnindex="' + selectedColumnIndex + '"][data-tutorial-selector-id="gridHeaderCell"]')) {
document.querySelector('[data-columnindex="' + (selectedColumnIndex - 1) + '"][data-tutorial-selector-id="gridHeaderCell"]').firstElementChild.classList.remove('column-selected-by-hotkey')
document.querySelector('[data-columnindex="' + selectedColumnIndex + '"][data-tutorial-selector-id="gridHeaderCell"]').firstElementChild.classList.add('column-selected-by-hotkey')
}
else {
document.querySelector('[data-columnindex="' + (selectedColumnIndex - 1) + '"][data-tutorial-selector-id="gridHeaderCell"]').firstElementChild.classList.remove('column-selected-by-hotkey')
selectedColumnIndex = 0
document.querySelector('[data-columnindex="' + selectedColumnIndex + '"][data-tutorial-selector-id="gridHeaderCell"]').firstElementChild.classList.add('column-selected-by-hotkey')
}
}
}
function rightClickSelectedFieldHeader() {
// Right click on selected field to bring up the field menu
let element = document.querySelector('.column-selected-by-hotkey')
element.classList.remove('column-selected-by-hotkey')
let ev2 = new MouseEvent("contextmenu", {
bubbles: true,
cancelable: false,
button: 2,
buttons: 0,
clientX: element.getBoundingClientRect().x,
clientY: element.getBoundingClientRect().y
});
element.dispatchEvent(ev2);
document.querySelector("[data-tutorial-selector-id='columnMenuItem-changeType']").classList.add('columnMenuItem-selected-by-hotkey')
}
function clickSelectedFieldMenuItem() {
document.getElementsByClassName('columnMenuItem-selected-by-hotkey')[0].click()
}
function clearClass(currentClass) {
if (document.getElementsByClassName(currentClass)[0]) {
document.getElementsByClassName(currentClass)[0].classList.remove(currentClass)
}
}
function setNextMenuItem(direction) {
let currentMenuItem = document.getElementsByClassName('columnMenuItem-selected-by-hotkey')[0]
let currentSelectorId = currentMenuItem.dataset.tutorialSelectorId
let nextSelectorId
let array = ["columnMenuItem-changeType","columnMenuItem-addLookupFields","columnMenuItem-duplicate","columnMenuItem-insertLeft","columnMenuItem-insertRight","columnMenuItem-copyUrl","columnMenuItem-editDescription","columnMenuItem-editPermissions","columnMenuItem-sortAscending","columnMenuItem-sortDescending","columnMenuItem-addFilter","columnMenuItem-groupBy","columnMenuItem-showDependencies","columnMenuItem-hide","columnMenuItem-delete"]
let index = array.indexOf(currentSelectorId);
let indexToUse;
if (direction === "up") {
let next = index - 1
if(next >= 0){
if(document.querySelector("[data-tutorial-selector-id='" + array[next] + "']")){
indexToUse = next
}
else{
indexToUse = next - 1
}
}
else{
indexToUse = array.length
}
}
else {
let next = index + 1
if(next < array.length){
if(document.querySelector("[data-tutorial-selector-id='" + array[next] + "']")){
indexToUse = next
}
else{
indexToUse = next + 1
}
}
else{
indexToUse = 0
}
}
currentMenuItem.classList.remove('columnMenuItem-selected-by-hotkey')
console.log(array[indexToUse])
document.querySelector("[data-tutorial-selector-id='" + array[indexToUse] + "']").classList.add('columnMenuItem-selected-by-hotkey')
}
function simulateMouseClick(targetNode) {
function triggerMouseEvent(targetNode, eventType) {
var clickEvent = document.createEvent('MouseEvents');
clickEvent.initEvent(eventType, true, true);
targetNode.dispatchEvent(clickEvent);
}
["mouseover", "mousedown", "mouseup", "click"].forEach(function (eventType) {
triggerMouseEvent(targetNode, eventType);
});
}