Last active
July 16, 2019 20:07
-
-
Save DanH42/ab83bf856abc7731bec0b0c35bef155d to your computer and use it in GitHub Desktop.
Override keyboard shortcuts in Gmail
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ==UserScript== | |
// @name Gmail Keyboard Shortcuts | |
// @namespace com.gmail.keyboard | |
// @version 1.0.4 | |
// @description Override keyboard shortcuts in Gmail | |
// @author Dan Hlavenka | |
// @updateURL https://gist.githubusercontent.com/DanH42/ab83bf856abc7731bec0b0c35bef155d/raw | |
// @match https://mail.google.com/mail/* | |
// @grant none | |
// ==/UserScript== | |
// Custom shortcuts | |
var KEYBOARD_OVERRIDES = { | |
"sa": "*a", // Select all | |
"sn": "*n", // Select none | |
"sr": "*r", // Select read | |
"su": "*u", // Select unread | |
"s*": "*s", // Select starred | |
"st": "*t", // Select unstarred | |
"mr": "I", // Mark read | |
"mu": "U", // Mark unread | |
"g*": "gs", // Goto starred | |
"gs": "gt", // Goto Sent | |
"*": "s", // Star | |
"`c": "c", // Compose | |
"`/": "/", // Search | |
// Macros | |
"zs": "*u I *n gi u", // Select unread, mark read, select none, goto inbox, reload | |
// Unchanged shortcuts | |
"gi": "gi", // Goto inbox | |
"gd": "gd", // Goto Drafts | |
"ga": "ga", // Goto all mail | |
"u": "u", // Back to threadlist (refresh) | |
"j": "j", // Down | |
"k": "k", // Up | |
"x": "x", // Selext conversation | |
"e": "e", // Archive | |
"+": "+", // Mark important | |
"=": "+", // Mark important, but you didn't hold shift | |
"-": "-", // Mark unimportant | |
"?": "?", // Help (won't be accurate, but useful for editing shortcuts here) | |
}; | |
// Time in ms to wait between simulated keypresses | |
var MACRO_DELAY = 15; | |
var KEY_EVENTS = ["keydown", "keypress", "keyup"]; | |
(function(){ | |
'use strict'; | |
// Don't run in frames | |
if(window !== parent) | |
return; | |
var capsWarningTimeout = null; | |
// Check if the currently focused element is a textbox | |
var isActiveElementEditable = function(){ | |
if(document.activeElement.isContentEditable) | |
return true; | |
if(["INPUT", "TEXTAREA", "IFRAME"].indexOf(document.activeElement.tagName) !== -1) | |
return true; | |
return false; | |
}; | |
// Simulate a keypress | |
var pressKey = function(key){ | |
// Space = pause | |
if(key === " ") | |
return; | |
var el = document.activeElement; | |
var keyCode = key.charCodeAt(0); | |
for(var i = 0; i < KEY_EVENTS.length; i++) | |
setTimeout(keyEvent.bind(this, el, KEY_EVENTS[i], key, keyCode), i * 5); | |
}; | |
var keyEvent = function(el, type, key, keyCode){ | |
var event = document.createEvent("Events"); | |
if(event.initEvent) | |
event.initEvent(type, true, true); | |
event.keyCode = keyCode; | |
event.charCode = keyCode; | |
event.which = keyCode; | |
event.key = key; | |
if(key.toLowerCase() !== key) | |
event.shiftKey = true; | |
el.dispatchEvent(event); | |
}; | |
// Keep track of multi-character shortcuts | |
var stack = ""; | |
var stackTimeout = null; | |
var startStackTimeout = function(){ | |
if(stackTimeout) | |
clearTimeout(stackTimeout); | |
stackTimeout = setTimeout(clearStack, 500); | |
}; | |
var clearStack = function(){ | |
if(stackTimeout){ | |
clearTimeout(stackTimeout); | |
stackTimeout = null; | |
} | |
stack = ""; | |
}; | |
// Check if a keypress matches at least one viable override | |
var isPossibleMatch = function(key){ | |
var search = stack + key; | |
for(var command in KEYBOARD_OVERRIDES){ | |
if(command.startsWith(search)) | |
return true; | |
} | |
return false; | |
}; | |
var handleKeyEvent = function(e){ | |
if( | |
// Don't respond to our own events | |
!e.isTrusted || | |
// Don't capture keypresses that use modifier keys | |
e.ctrlKey || e.altKey || e.metaKey || | |
// Don't capture keypresses in text boxes | |
isActiveElementEditable() || | |
// Only handle single-character keys | |
e.key.length !== 1 | |
) | |
return; | |
// Detect caps lock | |
if(e.key !== e.key.toLowerCase() && e.shiftKey === false) | |
showCapsWarning(); | |
// Suppress keyup/keydown events without any additional logic | |
if(e.type !== "keypress"){ | |
e.stopPropagation(); | |
return; | |
} | |
// Only add a keypress to the stack if it's a potential match for a command | |
if(isPossibleMatch(e.key)){ | |
stack += e.key; | |
if(KEYBOARD_OVERRIDES[stack]){ | |
var action = KEYBOARD_OVERRIDES[stack]; | |
console.log("MATCH", stack); | |
setTimeout(console.log.bind(this, "DONE"), action.length * MACRO_DELAY); | |
clearStack(); | |
// Perform an action if we have a match | |
for(var i = 0; i < action.length; i++) | |
setTimeout(pressKey.bind(this, action[i]), i * MACRO_DELAY); | |
}else{ | |
// If the keypress isn't an exact match (multi-key shortcuts), reset the timer | |
startStackTimeout(); | |
} | |
}else{ | |
// If the keypress won't result in a valid command, clear the stack | |
clearStack(); | |
} | |
// Cancel the keypress | |
e.stopPropagation(); | |
e.preventDefault(); | |
return false; | |
}; | |
var showCapsWarning = function(){ | |
capsWarningTimeout && clearTimeout(capsWarningTimeout); | |
var oldCapsWarning = document.getElementById('capsWarning'); | |
oldCapsWarning && oldCapsWarning.remove(); | |
var capsWarning = document.createElement('div'); | |
capsWarning.id = "capsWarning"; | |
capsWarning.style.background = "rgba(0, 0, 0, .9)"; | |
capsWarning.style.color = "white"; | |
capsWarning.style.position = "fixed"; | |
capsWarning.style.top = "50%"; | |
capsWarning.style.left = "50%"; | |
capsWarning.style.width = "200px"; | |
capsWarning.style.height = "40px"; | |
capsWarning.style.marginTop = "-30px"; | |
capsWarning.style.marginLeft = "-110px"; | |
capsWarning.style.fontSize = "32px"; | |
capsWarning.style.textAlign = "center"; | |
capsWarning.style.padding = "20px"; | |
capsWarning.style.borderRadius = "10px"; | |
capsWarning.style.opacity = "1"; | |
capsWarning.style.transition = "opacity 2s"; | |
capsWarning.innerText = "Caps Lock"; | |
document.body.appendChild(capsWarning); | |
capsWarningTimeout = setTimeout(function(){ | |
capsWarning.style.opacity = "0"; | |
capsWarningTimeout = setTimeout(function(){ | |
capsWarning && capsWarning.remove(); | |
capsWarningTimeout = null; | |
}, 2100); | |
}, 100); | |
}; | |
for(var i = 0; i < KEY_EVENTS.length; i++) | |
window.addEventListener(KEY_EVENTS[i], handleKeyEvent, true); | |
})(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment