Created
August 29, 2023 14:37
-
-
Save kafene/1ad8cc4c60129deb3c4f13b78919d8da to your computer and use it in GitHub Desktop.
Tab deque modified (tabs.moveInSuccession)
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
/* jshint esversion:11 */ | |
// Maps each window ID to an array of its tab IDs | |
const deques = {}; | |
// New background tabs will be added after the current tab instead of at the end of the deque | |
let addBackgroundTabsAfterCurrent = false; | |
// Create deque for this windowId if it does not exist | |
// Omit hidden tabs, keep discarded tabs at the end. | |
function ensureDeque(windowId, tabs) { | |
if (deques[windowId] == null) { | |
const tabIds = tabs.filter(tab => !tab.hidden && !tab.discarded).map(tab => tab.id); | |
const discardedTabIds = tabs.filter(tab => tab.discarded).map(tab => tab.id); | |
deques[windowId] = [...tabIds, ...discardedTabIds]; | |
} | |
} | |
function runOnActiveTab(callback) { | |
browser.tabs.query({active: true, currentWindow: true}).then(tabs => { | |
for (let tab of tabs) { | |
callback(tab); | |
} | |
}).catch(console.error); | |
} | |
function sendTabToEndOfDeque(tab) { | |
deques[tab.windowId] = deques[tab.windowId].filter(t => t !== tab.id).concat([tab.id]); | |
browser.tabs.update(deques[tab.windowId][0], {active: true}).then(() => { | |
return browser.tabs.moveInSuccession(deques[tab.windowId]); | |
}).catch(console.error); | |
} | |
function selectTabFromEndOfDeque(tab) { | |
const tabId = deques[tab.windowId][deques[tab.windowId].length - 1]; | |
deques[tab.windowId] = [tabId].concat(deques[tab.windowId].filter(t => t !== tabId)); | |
browser.tabs.update(deques[tab.windowId][0], {active: true}).then(() => { | |
return browser.tabs.moveInSuccession(deques[tab.windowId]); | |
}).catch(console.error); | |
} | |
function selectPreviousTab(currentTab) { | |
const windowId = currentTab.windowId; | |
browser.windows.get(windowId, {populate: true}).then(({ tabs }) => { | |
let prevTab = tabs.find(tab => tab.index === currentTab.index - 1) || tabs[tabs.length - 1]; | |
deques[windowId] = [prevTab.id, ...deques[windowId].filter(v => v !== prevTab.id)]; | |
return browser.tabs.update(prevTab.id, {active: true}); | |
}).catch(console.error); | |
} | |
function selectNextTab(currentTab) { | |
browser.windows.get(tab.windowId, {populate: true}).then(({ tabs }) => { | |
let nextTab = tabs.find(tab => tab.index === currentTab.index + 1) || tabs[0]; | |
deques[windowId] = [nextTab.id, ...deques[windowId].filter(v => v !== nextTab.id)]; | |
return browser.tabs.update(nextTab.id, {active: true}); | |
}).catch(console.error); | |
} | |
function onCommand(command) { | |
if (command === 'toggle-add-background-tabs-after-current') { | |
addBackgroundTabsAfterCurrent = !addBackgroundTabsAfterCurrent; | |
} else if (command === 'send-tab-to-end-of-tabdeque') { | |
runOnActiveTab(tab => sendTabToEndOfDeque(tab)); | |
} else if (command === 'select-tab-from-end-of-tabdeque') { | |
runOnActiveTab(tab => selectTabFromEndOfDeque(tab)); | |
} else if (command === 'select-previous-tab') { | |
runOnActiveTab(tab => selectPreviousTab(tab)); | |
} else if (command === 'select-next-tab') { | |
runOnActiveTab(tab => selectNextTab(tab)); | |
} | |
} | |
function onMenuClicked(info, tab) { | |
if (info.menuItemId === 'send-tab-to-end-of-tabdeque') { | |
return runOnActiveTab(tab => sendTabToEndOfDeque(tab)); | |
} | |
} | |
function onTabActivated(info) { | |
const {windowId, tabId} = info; | |
deques[windowId] = [tabId].concat(deques[windowId].filter(t => t !== tabId)); | |
if (info.previousTabId != null) { | |
browser.tabs.moveInSuccession([tabId], info.previousTabId).catch(console.error); | |
} | |
} | |
function onTabCreated(tab) { | |
if (deques[tab.windowId].indexOf(tab.id) === -1) { | |
if (addBackgroundTabsAfterCurrent) { | |
deques[tab.windowId].splice(1, 0, tab.id); | |
} else { | |
deques[tab.windowId].push(tab.id); | |
} | |
} | |
} | |
function onTabAttached(tabId, info) { | |
deques[info.newWindowId].unshift(tabId); | |
} | |
function onTabDetached(tabId, info) { | |
deques[info.windowId] = deques[info.windowId].filter(t => t !== tabId); | |
} | |
function onTabRemoved(tabId, info) { | |
deques[info.windowId] = deques[info.windowId].filter(t => t !== tabId); | |
} | |
// Handle tab hiding (experimental) | |
function onTabHiddenChange(tabId, change, tab) { | |
if (tab.hidden) { | |
deques[tab.windowId] = deques[tab.windowId].filter(t => t !== tab.id); | |
browser.tabs.moveInSuccession(deques[tab.windowId]).catch(console.error); | |
} else if (tab.active) { | |
deques[tab.windowId].unshift(tab.id); | |
} else { | |
deques[tab.windowId].push(tab.id); | |
} | |
} | |
// Handle tab discards - send discarded tabs to end of deque | |
function onTabDiscardedChange(tabId, change, tab) { | |
if (tab.discarded) { | |
deques[tab.windowId] = deques[tab.windowId].filter(t => t !== tab.id).concat([tab.id]); | |
browser.tabs.moveInSuccession(deques[tab.windowId]).catch(console.error); | |
} | |
} | |
browser.windows.onCreated.addListener((info) => { | |
ensureDeque(info.windowId, info.tabs); | |
}); | |
browser.windows.onRemoved.addListener(windowId => { | |
delete deques[windowId]; | |
}); | |
// Initialize on startup | |
browser.windows.getAll({populate: true, windowTypes: ['normal']}).then(windows => { | |
for (const w of windows) { | |
ensureDeque(w.id, w.tabs); | |
} | |
browser.menus.create({ | |
id: 'send-tab-to-end-of-tabdeque', | |
title: 'Send tab to end of TabDeque', | |
contexts: ['tab'], | |
}); | |
browser.menus.onClicked.addListener(onMenuClicked); | |
browser.commands.onCommand.addListener(onCommand); | |
browser.tabs.onCreated.addListener(onTabCreated); | |
browser.tabs.onRemoved.addListener(onTabRemoved); | |
browser.tabs.onActivated.addListener(onTabActivated); | |
browser.tabs.onAttached.addListener(onTabAttached); | |
browser.tabs.onDetached.addListener(onTabDetached); | |
browser.tabs.onUpdated.addListener(onTabHiddenChange, {properties: ['hidden']}); | |
browser.tabs.onUpdated.addListener(onTabDiscardedChange, {properties: ['discarded']}); | |
}).catch(console.error); |
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
{ | |
"manifest_version": 2, | |
"name": "Tab Deque", | |
"version": "2.1.7", | |
"description": "A webextension for better tab handling. Inspired by Opera 12.", | |
"homepage_url": "https://github.com/sblask/webextension-tab-deque", | |
"browser_specific_settings": {"gecko": {"id": "tabdeque@kafene"}}, | |
"author": "Sebastian Blask", | |
"background": {"scripts": ["background.js"], "persistent": true}, | |
"icons": {"48": "icon.svg", "96": "icon.svg"}, | |
"permissions": ["menus", "tabs"], | |
"commands": { | |
"select-tab-from-end-of-tabdeque": { | |
"description": "Select tab from end of TabDeque", | |
"suggested_key": {"default": "Ctrl+Up"} | |
}, | |
"send-tab-to-end-of-tabdeque": { | |
"description": "Send tab to end of TabDeque", | |
"suggested_key": {"default": "Ctrl+Down"} | |
}, | |
"select-previous-tab": { | |
"description": "Select previous tab", | |
"suggested_key": {"default": "Alt+Left"} | |
}, | |
"select-next-tab": { | |
"description": "Select next tab", | |
"suggested_key": {"default": "Alt+Right"} | |
}, | |
"toggle-add-background-tabs-after-current": { | |
"description": "Toggle between adding new background tabs to the end of the deque / after the current tab", | |
"suggested_key": {"default": "Ctrl+Alt+B"} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment