Help

More Airtable hotkeys / shortcuts!

2168 0
cancel
Showing results for 
Search instead for 
Did you mean: 
TheTimeSavingCo
18 - Pluto
18 - Pluto

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

Demo

Hotkey Demo

Change Log

  • 7 Oct 2022
    • Updated to handle new field menu items
  • 24 Mar 2023
    • Updated to handle new field option layout
    • Firefox now uses MetaLeft; script updated to use it
  • 28 Mar 2023
    • Updated to handle new formula field HTML
  • 29 June 2024
    • Updated to handle new create button selector ID

Shortcut List

⌘/Left Alt + ⇧ Left Shift + 'Add a new fieldAdd a field
⌘/Left Alt + ⇧ Right ShiftCustomize Field modal - Click “Save”Save field
⌘/Left Alt + 'Next field headerNext header
⌘/Left Alt + ;Previous field headerPrevious header
sNext field menu optionNext field menu option
wPrevious field menu optionPrevious field menu option
⌘/Left Alt + ↵ Enter“Click” selected field menu optionSelect field menu option
⌘/Left Alt + ↵ EnterOpen field menu for selected fieldOpen field menu
EscapeDeselectDeselect
⌘/Left Alt + ⇧ Left Shift + 1Select first cellSelect first cell

Want another hotkey? Tell me about it and I’ll see what I can do!

How to use

  1. Install Tampermonkey
  2. Open the extension, click “Create a new script”, and paste the code found at the end of the post into it and save
  3. You’re done!
    Script installation

Notes

I’ve tested this mostly on Firefox on a Mac, but have also tested it a bit on:

  1. Chrome - Mac
  2. Firefox - Windows
  3. Chrome - Windows

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);
    });
}

 

 

 

 

 

 

 
0 Replies 0