Skip to content

Commit

Permalink
Implement over-screen tab search UI (closes #27)
Browse files Browse the repository at this point in the history
Also closes #16. This commit also changes the popup action from tab search to
options page (which closes #19). I performed rather crude surgery to glue the
hotkeys.js and tab_search.js (and corresponding CSS) together; I'll need to
clean this up a lot when I refactor to use modules (#51).
  • Loading branch information
jchang504 committed Jul 27, 2017
1 parent ceaa1ef commit 004ff6c
Show file tree
Hide file tree
Showing 9 changed files with 219 additions and 158 deletions.
21 changes: 14 additions & 7 deletions background.js
Original file line number Diff line number Diff line change
Expand Up @@ -95,12 +95,6 @@ function loadHotkeys() {
});
}

// Open a new tab of the tab search page.
function openTabSearch() {
LOG_INFO("Open tab search");
createNewTab(SEARCH_URL);
}

// Navigate to the most recently active still-existing tab before the current
// tab. Useful for quick alt+tab style switching between two tabs.
function navigateToPreviousTab() {
Expand Down Expand Up @@ -247,7 +241,13 @@ chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
closeCurrentTab();
}
else if (hotkey == TAB_SEARCH_KEYVAL) {
openTabSearch();
LOG_INFO("Open tab search on current tab");
chrome.tabs.query({}, function(tabs) {
sendResponse({[SEARCH_TABS_MSG]: tabs});
});
// Indicate that sendResponse will be called asynchronously; keep
// the channel open. See https://stackoverflow.com/a/20077854.
return true;
}
else if (hotkey == NAV_PREVIOUS_KEYVAL) {
navigateToPreviousTab();
Expand All @@ -265,6 +265,13 @@ chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
updateHoldKey();
loadHotkeys();
}
// Received search selection; navigate to it.
else if (request.hasOwnProperty(SEARCH_SELECT_MSG)) {
LOG_INFO("Received search selection");
var search_selection = request[SEARCH_SELECT_MSG];
navigateToTab(search_selection[TAB_ID_KEY],
search_selection[WINDOW_ID_KEY]);
}
});

// Load hotkeys at background script start.
Expand Down
131 changes: 96 additions & 35 deletions hotkeys.js
Original file line number Diff line number Diff line change
@@ -1,29 +1,52 @@
var BODY_SELECTOR = "body";
// NOTE: This section is tightly coupled with the CSS in overlay.css.
// NOTE: This part tightly coupled with the CSS in overlay.css, tab_search.css.
var OVERLAY_HTML = '\
<div id="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 = "#overlay";
var OVERLAY_HOLDING_CLASS = "holding";
var OVERLAY_IMG_SELECTOR = "#overlay > img";
var OVERLAY_SPAN_SELECTOR = "#overlay > span";
// End tightly coupled section with overlay.css.
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 {
}
else if (!in_tab_search) {
// If the tab search animation is currently running, don't hide the
// overlay on keyup.
$(OVERLAY_SELECTOR).hide();
}
}
Expand All @@ -33,52 +56,90 @@ function setHotkeyString(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();
});
}

function sendHotkeyMessage(hotkey) {
LOG_INFO("Send hotkey: " + hotkey);
chrome.runtime.sendMessage({[HOTKEY_MSG]: 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.hasOwnProperty(SEARCH_TABS_MSG)) {
search_tabs = response[SEARCH_TABS_MSG];
openTabSearch();
}
});
}

function keydownHandler(e) {
// When hold key pressed, block text entry and wait for hotkey.
if (e.key == hold_key) {
if (!holding) {
LOG_INFO("Holding for hotkey...");
chrome.runtime.sendMessage({[HOLD_KEY_MSG]: true});
setHoldKeyStatus(true);
// For cancelling tab search.
if (in_tab_search) {
if (e.key == hold_key) {
in_tab_search = false;
$(OVERLAY_SELECTOR).hide();
$(SEARCH_BAR_SELECTOR + ", " + SEARCH_RESULTS_SELECTOR).hide();
$(OVERLAY_SELECTOR).css(OVERLAY_ANIMATION_UNDO);
}
// Prevent default behavior of hold key.
e.preventDefault();
}
if (holding) {
// Capture [A-Za-z].
var ascii_value = e.key.charCodeAt(0);
if (e.key.length == 1 &&
65 <= ascii_value && ascii_value <= 90 ||
97 <= ascii_value && ascii_value <= 122) {
setHotkeyString(hotkey + e.key);
// Ignore hold key when in tab search.
else {
// When hold key pressed, block text entry and wait for hotkey.
if (e.key == hold_key) {
if (!holding) {
LOG_INFO("Holding for hotkey...");
chrome.runtime.sendMessage({[HOLD_KEY_MSG]: true});
setHoldKeyStatus(true);
}
// Prevent default behavior of hold key.
e.preventDefault();
}
// Capture built-in hotkeys. Send them immediately so that the user can
// repeatedly use them without releasing the hold key.
else if (BUILT_IN_HOTKEYS.includes(e.key)) {
sendHotkeyMessage(e.key);
setHotkeyString("");
if (holding) {
// Capture [A-Za-z].
var ascii_value = e.key.charCodeAt(0);
if (e.key.length == 1 &&
65 <= ascii_value && ascii_value <= 90 ||
97 <= ascii_value && ascii_value <= 122) {
setHotkeyString(hotkey + e.key);
}
// Capture built-in hotkeys. Send them immediately so that the user
// can repeatedly use them without releasing the hold key.
else if (BUILT_IN_HOTKEYS.includes(e.key)) {
sendHotkeyMessage(e.key);
setHotkeyString("");
}
e.stopPropagation();
e.preventDefault();
}
e.stopPropagation();
e.preventDefault();
}
}

function keyupHandler(e) {
// When hold key released, unblock text entry and send any hotkey entered.
if (e.key == hold_key) {
if (hotkey.length > 0) {
sendHotkeyMessage(hotkey);
setHotkeyString("");
// 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("");
}
e.stopPropagation();
LOG_INFO("Released for hotkey.");
chrome.runtime.sendMessage({[HOLD_KEY_MSG]: false});
setHoldKeyStatus(false);
}
e.stopPropagation();
LOG_INFO("Released for hotkey.");
chrome.runtime.sendMessage({[HOLD_KEY_MSG]: false});
setHoldKeyStatus(false);
}
}

Expand Down
6 changes: 3 additions & 3 deletions manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,16 @@
],
"browser_action": {
"default_icon": "icons/icon48.png",
"default_popup": "tab_search.html"
"default_popup": "options.html"
},
"background": {
"scripts": ["shared_utils.js", "background.js"]
},

"content_scripts": [{
"matches": ["<all_urls>"],
"js": ["jquery-3.2.1.min.js", "shared_utils.js", "hotkeys.js"],
"css": ["overlay.css"],
"js": ["jquery-3.2.1.min.js", "shared_utils.js", "hotkeys.js", "fuse.js", "tab_search.js"],
"css": ["overlay.css", "tab_search.css"],
"run_at": "document_start"
}],
"web_accessible_resources": [
Expand Down
3 changes: 2 additions & 1 deletion options.css
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
body {
background-color: lightgrey;
min-width: 750px;
}

#hold_key_description {
Expand All @@ -19,7 +20,7 @@ input[name="hotkey"] {
}

input[name="target"], input[name="match_prefix"] {
width: 30em;
width: 25em;
}

th.checkbox {
Expand Down
2 changes: 1 addition & 1 deletion overlay.css
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
*/
display: none;
background-color: rgba(50, 50, 50, 0.7);
box-sizing: content-box;
box-sizing: border-box;
margin: 0;
padding: 10px;
line-height: initial;
Expand Down
2 changes: 2 additions & 0 deletions shared_utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ var HOLD_KEY_MSG = "hold_key";
var HOTKEY_MSG = "hotkey";
var REFRESH_MSG = "refresh";
var UPDATE_HOLD_KEY_MSG = "update_hold_key";
var SEARCH_TABS_MSG = "search_tabs";
var SEARCH_SELECT_MSG = "search_select";
var TAB_ID_KEY = "tab_id";
var WINDOW_ID_KEY = "window_id";

Expand Down
52 changes: 43 additions & 9 deletions tab_search.css
Original file line number Diff line number Diff line change
@@ -1,19 +1,53 @@
html {
min-width: 400px;
#search_bar, #search_results {
display: none;
}

#results tr.selected td {
background-color: #cfe3e8;
#search_bar > label {
vertical-align: middle;
color: white;
font-size: 1.5em;
margin: 0.5em;
}

#search_bar > input {
vertical-align: middle;
}

#search_results {
max-height: 80%;
overflow: auto;
}

#search_results a {
color: rgb(210, 230, 235);
}

#search_results a:hover {
text-decoration: none;
cursor: pointer;
}

#search_results td {
padding: 1em;
}

#search_results p {
margin-top: 0;
margin-bottom: 0.25em;
}

#search_results tr.selected td {
background-color: rgb(39, 73, 93);
}

.result-title {
color: white;
font-size: 1.2em;
color: black;
margin-bottom:0px;
}

.favicon {
max-height: 1.2em;
max-width: 1.2em;
margin-right: 5px;
max-height: 1em;
max-width: 1em;
vertical-align: middle;
margin-right: 0.5em;
}
34 changes: 0 additions & 34 deletions tab_search.html

This file was deleted.

Loading

0 comments on commit 004ff6c

Please sign in to comment.