-
Notifications
You must be signed in to change notification settings - Fork 1
/
hotkeys.js
219 lines (207 loc) · 7.17 KB
/
hotkeys.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
var BODY_SELECTOR = "body";
// NOTE: This part tightly coupled with the CSS in overlay.css, tab_search.css.
var OVERLAY_HTML = '\
<div id="keeptabs_overlay">\
<img>\
<span></span>\
<div id="search_bar">\
<label for="fuzzy_input">Tab Search</label>\
<input type="text" id="fuzzy_input" placeholder="google.com">\
</div>\
<div id="search_results">\
<table>\
<tbody>\
</tbody>\
</table>\
</div>\
</div>';
var OVERLAY_SELECTOR = "#keeptabs_overlay";
var OVERLAY_HOLDING_CLASS = "holding";
var OVERLAY_IMG_SELECTOR = "#keeptabs_overlay > img";
var OVERLAY_SPAN_SELECTOR = "#keeptabs_overlay > span";
var OVERLAY_EXPAND_ANIMATION = {"height": "100%", "width": "100%"};
var OVERLAY_EXPAND_TIME = 200;
var OVERLAY_ANIMATION_UNDO = {"height": "initial", "width": "initial"};
var SEARCH_BAR_SELECTOR = "#search_bar";
var SEARCH_RESULTS_SELECTOR = "#search_results";
// End tightly coupled part with overlay.css, tab_search.css.
var SRC = "src";
var DISPLAY = "display";
var INLINE = "inline";
// Global state.
// This gets updated by reading from storage before key event handlers are
// attached. See bottom.
var hold_key = null;
var holding = false;
var hotkey = "";
// Used in tab_search.js
var search_tabs = [];
var in_tab_search = false;
function setHoldKeyStatus(is_holding) {
holding = is_holding;
if (is_holding) {
$(OVERLAY_SELECTOR).show();
}
else if (!in_tab_search) {
// If the tab search animation is currently running, don't hide the
// overlay on keyup.
$(OVERLAY_SELECTOR).hide();
}
}
function setHotkeyString(current_hotkey) {
hotkey = current_hotkey;
$(OVERLAY_SPAN_SELECTOR).text(hotkey);
}
// Animate the overlay to expand into tab search UI.
function openTabSearch() {
in_tab_search = true;
setHoldKeyStatus(false);
$(OVERLAY_SELECTOR).animate(OVERLAY_EXPAND_ANIMATION, OVERLAY_EXPAND_TIME,
function() {
$(FUZZY_INPUT_SELECTOR).val("");
populate();
$(SEARCH_BAR_SELECTOR).css(DISPLAY, INLINE);
$(SEARCH_RESULTS_SELECTOR).show();
$(FUZZY_INPUT_SELECTOR).focus();
});
}
// Close the tab search UI.
function closeTabSearch() {
in_tab_search = false;
$(OVERLAY_SELECTOR).hide();
$(SEARCH_BAR_SELECTOR + ", " + SEARCH_RESULTS_SELECTOR).hide();
$(OVERLAY_SELECTOR).css(OVERLAY_ANIMATION_UNDO);
}
function sendHotkeyMessage(hotkey) {
LOG_INFO("Send hotkey: " + hotkey);
chrome.runtime.sendMessage({[HOTKEY_MSG]: hotkey}, function(response) {
// If the background script responds with a SEARCH_TABS_MSG, then this
// tab sent a tab search hotkey and should open up the search UI with
// response tabs.
if (response && response.hasOwnProperty(SEARCH_TABS_MSG)) {
search_tabs = response[SEARCH_TABS_MSG];
openTabSearch();
}
});
}
// Extract the letter typed from the keydown_event, including capitalization.
function extractLetter(keydown_event) {
const code = keydown_event.code;
var letter = code.charAt(code.length - 1);
if (!keydown_event[SHIFT_MODIFIER]) {
letter = letter.toLowerCase();
}
return letter;
}
function keydownHandler(e) {
// For cancelling tab search.
if (in_tab_search) {
if (e.key == hold_key) {
closeTabSearch();
}
}
// Ignore hold key when in tab search.
if (!in_tab_search) {
// When hold key pressed, block text entry and wait for hotkey.
if (e.key == hold_key) {
if (!holding) {
LOG_INFO("Holding for hotkey...");
setHoldKeyStatus(true);
}
e.stopImmediatePropagation();
e.preventDefault();
}
// If keydown event shows hold key pressed with built in hotkey,
// activate regardless of whether holding == true. Turn off the holding
// indicator.
else if (e[HOLD_KEY_TO_MODIFIER[hold_key]] &&
BUILT_IN_HOTKEYS.includes(e.code)) {
var modified_code = e.code;
if (e[SHIFT_MODIFIER]) {
modified_code += SHIFT;
}
sendHotkeyMessage(modified_code);
setHotkeyString("");
// Don't turn off hold key indicator for tab search animation.
if (e.code != TAB_SEARCH_CODE) {
setHoldKeyStatus(false);
}
e.stopImmediatePropagation();
e.preventDefault();
}
else if (holding) {
// Capture [A-Za-z].
if (e.code.startsWith(ALPHA_PREFIX)) {
const letter = extractLetter(e);
setHotkeyString(hotkey + letter);
e.stopImmediatePropagation();
e.preventDefault();
}
// Cancel holding on non-alphabetic keys, except for shift.
else if (e.key != SHIFT) {
setHotkeyString("");
setHoldKeyStatus(false);
// Don't stop propagation and prevent default here, to allow
// non-alphabetic browser and site shortcuts.
}
}
}
}
function keyupHandler(e) {
// Ignore hold key when in tab search.
if (!in_tab_search) {
// When hold key released, unblock text entry and send any hotkey
// entered.
if (e.key == hold_key) {
if (hotkey.length > 0) {
sendHotkeyMessage(hotkey);
setHotkeyString("");
}
LOG_INFO("Released for hotkey.");
setHoldKeyStatus(false);
e.stopImmediatePropagation();
e.preventDefault();
}
}
}
// Listen for hold key release and options changes messages.
chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
// Release the hold key on window change to prevent sticking.
if (request.hasOwnProperty(HOLD_RELEASE_MSG)) {
LOG_INFO("Received hold release; releasing hold key");
if (in_tab_search) {
closeTabSearch();
}
setHoldKeyStatus(false);
}
else if (request.hasOwnProperty(UPDATE_HOLD_KEY_MSG)) {
hold_key = request[UPDATE_HOLD_KEY_MSG];
}
});
chrome.storage.sync.get({
[HOLD_KEY_KEY]: HOLD_KEY_EMPTY,
[OS_KEY]: OS_EMPTY
}, function(items) {
hold_key = items[HOLD_KEY_KEY];
if (hold_key == HOLD_KEY_EMPTY) {
// No hold key saved in storage.
var os = items[OS_KEY];
if (os == OS_EMPTY) {
// This should never happen, unless the background script somehow
// failed to save the OS.
LOG_ERROR("No OS saved in chrome.storage.sync.");
hold_key = ALT;
}
else {
hold_key = OS_TO_DEFAULT_HOLD_KEY[os];
}
}
// Only add listeners once hold_key has been updated from options.
$(window).get(0).addEventListener(KEYDOWN, keydownHandler, true);
$(window).get(0).addEventListener(KEYUP, keyupHandler, true);
});
$(document).ready(function() {
// Add visual overlay UI.
$(BODY_SELECTOR).append(OVERLAY_HTML);
$(OVERLAY_IMG_SELECTOR).prop(SRC, chrome.extension.getURL(ICON_128_URL));
});