Skip to content
This repository was archived by the owner on Nov 17, 2022. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/tab.js
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,9 @@ Object.assign(SideTab, {
getAllTabsViews() {
return document.getElementsByClassName("tab");
},
getVisibleTabViews() {
return document.querySelectorAll(".tab:not(.hidden)");
},
_syncThrobberAnimations() {
requestAnimationFrame(() => {
if (!document.body.getAnimations) { // this API is available only in Nightly so far
Expand Down
18 changes: 18 additions & 0 deletions src/tabcenter.css
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
--tab-background-pinned: 0, 0%, 97%;
--tab-background-active: 0, 0%, 87%;
--tab-background-hover: 0, 0%, 91%;
--tab-background-selected: 0, 0%, 91%;
--tab-background-active-and-selected: 0, 0%, 82%;
--tab-border-color: hsla(0, 0%, 0%, 0.06);
--searchbox-background: #fff;
--primary-text-color: #18191a;
Expand Down Expand Up @@ -356,6 +358,14 @@ body[platform="win"] #searchbox-input:placeholder-shown {
background-color: hsl(var(--tab-background-hover));
}

.tab.selected {
background-color: hsl(var(--tab-background-selected));
}

.tab.active.selected {
background-color: hsl(var(--tab-background-active-and-selected));
}

/* Tab loading burst is disabled because it triggers on non top-level pages
(try it on GitHub).
If you don't mind that, you can re-enable the feature by disabling the
Expand Down Expand Up @@ -643,6 +653,14 @@ body[platform="win"] #searchbox-input:placeholder-shown {
--tab-background: var(--tab-background-hover);
}

.tab.selected > .tab-title-wrapper::after {
--tab-background: var(--tab-background-selected);
}

.tab.active.selected > .tab-title-wrapper::after {
--tab-background: var(--tab-background-active-and-selected);
}

.tab:hover > .tab-title-wrapper::after {
transform: translateX(0);
}
Expand Down
2 changes: 1 addition & 1 deletion src/tabcenter.html
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
<div id="newtab-menu" class="hidden"></div>
<label id="searchbox" class="topmenu-item">
<span id="searchbox-icon"></span>
<input type="text" id="searchbox-input" size="1" />
<input type="text" id="searchbox-input" size="1" autofocus="autofocus" />
</label>
<div id="settings" class="topmenu-button topmenu-item"></div>
</div>
Expand Down
46 changes: 44 additions & 2 deletions src/tabcenter.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ TabCenter.prototype = {
this.setupListeners();
browser.runtime.getPlatformInfo().then((platform) => {
document.body.setAttribute("platform", platform.os);
this.platform = platform.os;
});
},
setupListeners() {
Expand All @@ -38,13 +39,19 @@ TabCenter.prototype = {
this._searchBoxInput.addEventListener("input", (e) => {
this.sideTabList.filter(e.target.value);
});
this._searchBoxInput.addEventListener("focus", () => {
const onSearchboxFocus = () => {
searchbox.classList.add("focused");
this._newTabLabelView.classList.add("hidden");
});
};
this._searchBoxInput.addEventListener("focus", onSearchboxFocus);
// We won't get this message reliably since the item has autofocus.
if (document.activeElement === this._searchBoxInput) {
onSearchboxFocus();
}
this._searchBoxInput.addEventListener("blur", () => {
searchbox.classList.remove("focused");
this._newTabLabelView.classList.remove("hidden");
this.sideTabList.clearSelection();
});
this._newTabButtonView.addEventListener("click", () => {
if (!this._newTabMenuShown) {
Expand Down Expand Up @@ -85,6 +92,41 @@ TabCenter.prototype = {
windowId: this.windowId
});
});
this._searchBoxInput.addEventListener("keypress", e => {
let delta = 0;
switch (e.key) {
case "Enter":
// Select whatever is already selected. Clear the current search and
// selection by default, but allow users to keep them using shift+enter.
this.sideTabList.commitSelection(!e.shiftKey);
e.preventDefault();
break;
case "ArrowDown":
delta = 1;
break;
case "ArrowUp":
delta = -1;
break;
// Apple supports emacs-style movement keys (ctrl+{n,p,f,b,a,e}) in
// most text boxes/dropdowns/etc. Most users are completely unaware
// of it, but I think the relevant items are worth supporting here.
// (Think "next line" for Ctrl+N, and "previous line" for Ctrl+P)
case "n": case "N":
if (e.ctrlKey && this.platform === "mac") {
delta = 1;
}
break;
case "p": case "P":
if (e.ctrlKey && this.platform === "mac") {
delta = -1;
}
break;
}
if (delta !== 0) {
this.sideTabList.moveSelection(delta);
e.preventDefault();
}
});
browser.storage.onChanged.addListener(changes => {
if (changes.darkTheme) {
this.toggleTheme(changes.darkTheme.newValue);
Expand Down
110 changes: 110 additions & 0 deletions src/tablist.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ function SideTabList() {
this._tabsShrinked = false;
this.windowId = null;
this._filterActive = false;
this._selected = null;
this.view = document.getElementById("tablist");
this.pinnedview = document.getElementById("pinnedtablist");
this._wrapperView = document.getElementById("tablist-wrapper");
Expand Down Expand Up @@ -114,6 +115,7 @@ SideTabList.prototype = {
if (!this.checkWindow(tab)) {
return;
}
this.clearSelection();
if (changeInfo.hasOwnProperty("title")) {
this.setTitle(tab);
}
Expand Down Expand Up @@ -417,6 +419,7 @@ SideTabList.prototype = {
await browser.tabs.move(tab.id, { index: lastIndex });
},
clearSearch() {
this.clearSelection();
if (!this._filterActive) {
return;
}
Expand All @@ -442,6 +445,111 @@ SideTabList.prototype = {
this._moreTabsView.removeAttribute("hasMoreTabs");
}
this.maybeShrinkTabs();
if (!this._filterActive) {
this.clearSelection();
} else {
// If there's a currently selected tab, try and use it if possible.
let index = this._getInitialSelectionIndex(this._selected);
this.setSelectionIndex(index);
}
},
clearSelection() {
let selected = document.querySelector(".selected");
if (selected) {
selected.classList.remove("selected");
}
this._selected = null;
},
setSelectionIndex(num) {
this.clearSelection();
if (num < 0) {
return;
}
// This is a little awkward, but it could be worse.
let visible = SideTab.getVisibleTabViews();
if (num > visible.length) {
// Should this be an error? moveSelection is expected to clamp.
return;
}
visible[num].classList.add("selected");
this._selected = SideTab.tabIdForView(visible[num]);
this.scrollToTab(this._selected);
},
_getInitialSelectionIndex(goalTabId = null) {
// only visible tabs considered.
let numPinned = 0;
let numUnpinned = 0;
let goalIndex = -1;

let curIndex = 0; // Index ignoring invisible tabs.
for (let tab of this.tabs.values()) {
if (!tab.visible) {
continue;
}
if (tab.pinned) {
numPinned++;
} else {
numUnpinned++;
}
if (tab.id === goalTabId) {
goalIndex = curIndex;
}
++curIndex;
}
if (numUnpinned === 0 && numPinned === 0) {
return -1;
}
// If our goal tab is visible, return it's index.
if (goalIndex >= 0) {
return goalIndex;
}
// Otherwise, if there are unpinned tabs, we use the first unpinned tab.
if (numUnpinned > 0) {
return numPinned;
}
// If there are no unpinned tabs but there are pinned tabs, return the
// the first pinned tab (which is always 0)
if (numPinned > 0) {
return 0;
}
// Otherwise there are no visible tabs at all, so we return -1 to indicate
// that no selection should be used.
return -1;
},
moveSelection(delta) {
// This is awkward because we don't have a datastructure that can answer
// positional information about tabs cheaply.
let tabs = Array.from(SideTab.getAllTabsViews(), el => {
return this.tabs.get(SideTab.tabIdForView(el));
});
let visibleTabs = tabs.filter(tab => tab.visible);
// Note: this._selected can be null (if they start using the arrows when
// ther's nothing in the searchbox), and this will do the right thing here
// so long as tab.id is never null.
let curSelectedIndex = visibleTabs.findIndex(t => t.id === this._selected);
if (curSelectedIndex < 0) {
curSelectedIndex = this._getInitialSelectionIndex();
if (curSelectedIndex < 0) {
// No visible tabs, or hypothetically some other reason we shouldn't
// be selecting anything.
this.clearSelection();
return;
}
}
let nextIndex = curSelectedIndex + delta;
// Clamp to [0, visibleTabs.length), so that navigating up or down
// off the end sticks in place.
nextIndex = Math.max(0, Math.min(visibleTabs.length - 1, nextIndex));
this.setSelectionIndex(nextIndex);
},
commitSelection(clearSearch) {
if (this._selected === null) {
return;
}
browser.tabs.update(this._selected, {active: true});
if (clearSearch) {
this.clearSearch();
}
},
async populate(windowId) {
if (windowId && this.windowId === null) {
Expand Down Expand Up @@ -619,6 +727,8 @@ SideTabList.prototype = {
if (!sidetab) {
return;
}
// Clear our selection whenever the tab list changes. (Is this too eager?)
this.clearSelection();
let element = sidetab.view;
let parent = sidetab.pinned ? this.pinnedview : this.view;
let elements = SideTab.getAllTabsViews();
Expand Down