Skip to content
Closed
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
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,19 @@ This extension adds these actions:
Moves tabs from same domain with the selected tab's domain to current window.
* Group tabs from this domain
Groups tabs from same domain with the selected tab's domain.
* Moves current tab to a new window

Default extension icon action is "Merge windows and sort tabs". This can be changed from options page. You can select "do nothing" option to disable default action.

By default the extension ignores pinned tabs, popup and app windows for merge and sort actions. These can be changed from options page.

This extension does not cause tab groups to be ungrouped.

## Shortcut keys

| Command | Windows/Linux Shortcut | Mac Shortcut | Description |
|------------------------------|------------------------|----------------------|------------------------------------|
| Merge windows and sort tabs | Ctrl+Shift+1 | Command+Shift+1 | Merge all windows and sort tabs |
| Merge windows | Ctrl+Shift+0 | Command+Shift+0 | Merge all windows |
| Sort tabs | Ctrl+Shift+S | Command+Shift+S | Sort tabs |
| Move tab to new window | Ctrl+Shift+9 | Command+Shift+9 | Move the current tab to a new window|
243 changes: 152 additions & 91 deletions background.js
Original file line number Diff line number Diff line change
@@ -1,39 +1,73 @@
const DO_NOTHING = {
title: "Do nothing",
id: "do_nothing"
}
title: 'Do nothing',
id: 'do_nothing',
};

const MERGE_AND_SORT_ACTION = {
title: "Merge windows and sort tabs",
id: "merge_and_sort"
}
title: 'Merge windows and sort tabs',
id: 'merge_and_sort',
};

const MERGE_ACTION = {
title: "Merge windows",
id: "merge"
}
title: 'Merge windows',
id: 'merge',
};

const SORT_ACTION = {
title: "Sort tabs",
id: "sort"
}
title: 'Sort tabs',
id: 'sort',
};

const CLOSE_TABS_FROM_THIS_DOMAIN_ACTION = {
title: "Close tabs from this domain",
id: "close_tabs_from_this_domain"
}
title: 'Close tabs from this domain',
id: 'close_tabs_from_this_domain',
};

const MOVE_TABS_FROM_THIS_DOMAIN_ACTION = {
title: "Move tabs from this domain to this window",
id: "move_tabs_from_this_domain"
}
title: 'Move tabs from this domain to this window',
id: 'move_tabs_from_this_domain',
};

const GROUP_TABS_FROM_THIS_DOMAON_ACTION = {
title: "Group tabs from this domain",
id: "group_tabs_from_this_domain"
}
title: 'Group tabs from this domain',
id: 'group_tabs_from_this_domain',
};

const MOVE_TAB_TO_NEW_WINDOW_ACTION = {
title: 'Move current tab to a new window',
id: 'move_tab_to_new_window',
};

const ALL_ACTIONS = [
MERGE_AND_SORT_ACTION,
CLOSE_TABS_FROM_THIS_DOMAIN_ACTION,
GROUP_TABS_FROM_THIS_DOMAON_ACTION,
MERGE_ACTION,
MOVE_TAB_TO_NEW_WINDOW_ACTION,
MOVE_TABS_FROM_THIS_DOMAIN_ACTION,
// max 6 shown in the context menu
SORT_ACTION,
DO_NOTHING,
];

const ALL_ACTIONS = [DO_NOTHING, MERGE_AND_SORT_ACTION, MERGE_ACTION, SORT_ACTION, CLOSE_TABS_FROM_THIS_DOMAIN_ACTION, MOVE_TABS_FROM_THIS_DOMAIN_ACTION, GROUP_TABS_FROM_THIS_DOMAON_ACTION]
chrome.commands.onCommand.addListener((command) => {
switch (command) {
case 'merge-windows-and-sort-tabs':
mergeWindowsAndSortTabsAction();
break;
case 'merge-windows':
mergeWindowsAction();
break;
case 'sort-tabs':
sortTabsAction();
break;
case 'move-tab-to-new-window':
moveTabToNewWindowAction();
break;
default:
console.log(`Command ${command} not recognized.`);
}
});

async function getOptions() {
return await chrome.storage.sync.get({
Expand All @@ -42,179 +76,206 @@ async function getOptions() {
ignorePopupWindows: true,
ignoreAppWindows: true,
showDefaultActionPopup: true,
})
});
}

async function getTabsFromDomain(url) {
let options = await getOptions()
let options = await getOptions();
let tabs = await chrome.tabs.query({
groupId: chrome.tabGroups.TAB_GROUP_ID_NONE,
url: url.protocol + "//" + url.host + "/*",
url: url.protocol + '//' + url.host + '/*',
pinned: options.ignorePinnedTabs ? false : undefined,
})
return tabs
});
return tabs;
}

function baseAction(actionId) {
if (actionId == DO_NOTHING.id) {
//DO NOTHING
} else if (actionId == MERGE_AND_SORT_ACTION.id) {
mergeWindowsAndSortTabsAction()
mergeWindowsAndSortTabsAction();
} else if (actionId == MERGE_ACTION.id) {
mergeWindowsAction()
mergeWindowsAction();
} else if (actionId == SORT_ACTION.id) {
sortTabsAction()
sortTabsAction();
} else if (actionId == CLOSE_TABS_FROM_THIS_DOMAIN_ACTION.id) {
closeTabsFromCurrentDomainAction()
closeTabsFromCurrentDomainAction();
} else if (actionId == MOVE_TABS_FROM_THIS_DOMAIN_ACTION.id) {
moveTabsFromCurrentDomainAction()
moveTabsFromCurrentDomainAction();
} else if (actionId == GROUP_TABS_FROM_THIS_DOMAON_ACTION.id) {
groupTabsFromCurrentDomainAction()
groupTabsFromCurrentDomainAction();
} else if (actionId == MOVE_TAB_TO_NEW_WINDOW_ACTION.id) {
moveTabToNewWindowAction();
}
}

async function mergeWindowsAndSortTabsAction() {
await mergeWindows()
await sortTabs()
await mergeWindows();
await sortTabs();
}

async function mergeWindowsAction() {
await mergeWindows()
await mergeWindows();
}

async function sortTabsAction() {
await sortTabs()
await sortTabs();
}

async function closeTabsFromCurrentDomainAction() {
let selectedTab = (await chrome.tabs.query({ active: true, currentWindow: true }))[0]
let url = new URL(selectedTab.url)
let tabs = await getTabsFromDomain(url)
let selectedTab = (await chrome.tabs.query({ active: true, currentWindow: true }))[0];
let url = new URL(selectedTab.url);
let tabs = await getTabsFromDomain(url);
for (let tab of tabs) {
await chrome.tabs.remove(tab.id)
await chrome.tabs.remove(tab.id);
}
}

async function moveTabsFromCurrentDomainAction() {
let selectedTab = (await chrome.tabs.query({ active: true, currentWindow: true }))[0]
let url = new URL(selectedTab.url)
let tabs = await getTabsFromDomain(url)
let selectedTab = (await chrome.tabs.query({ active: true, currentWindow: true }))[0];
let url = new URL(selectedTab.url);
let tabs = await getTabsFromDomain(url);
for (let tab of tabs) {
await chrome.tabs.move(tab.id, { windowId: selectedTab.windowId, index: -1 })
await chrome.tabs.move(tab.id, { windowId: selectedTab.windowId, index: -1 });
if (tab.pinned == true) {
await chrome.tabs.update(tab.id, { pinned: true })
await chrome.tabs.update(tab.id, { pinned: true });
}
}
}

async function groupTabsFromCurrentDomainAction() {
let selectedTab = (await chrome.tabs.query({ active: true, currentWindow: true }))[0]
let url = new URL(selectedTab.url)
let tabs = await getTabsFromDomain(url)
let tabIds = tabs.map(tab => tab.id)
let existingGroups = await chrome.tabGroups.query({ title: url.host })
let selectedTab = (await chrome.tabs.query({ active: true, currentWindow: true }))[0];
let url = new URL(selectedTab.url);
let tabs = await getTabsFromDomain(url);
let tabIds = tabs.map((tab) => tab.id);
let existingGroups = await chrome.tabGroups.query({ title: url.host });
if (existingGroups.length > 0) {
let existingGroupId = existingGroups[0].id
await chrome.tabs.group({ groupId: existingGroupId, tabIds: tabIds })
let existingGroupId = existingGroups[0].id;
await chrome.tabs.group({ groupId: existingGroupId, tabIds: tabIds });
} else {
let groupId = await chrome.tabs.group({ tabIds: tabIds })
await chrome.tabGroups.update(groupId, { title: url.host })
let groupId = await chrome.tabs.group({ tabIds: tabIds });
await chrome.tabGroups.update(groupId, { title: url.host });
}
}

async function mergeWindows() {
let options = await getOptions()
let currentWindow = await chrome.windows.getCurrent()
let windows = await chrome.windows.getAll({ populate: true })
let options = await getOptions();
let currentWindow = await chrome.windows.getCurrent();
let windows = await chrome.windows.getAll({ populate: true });

for (let window of windows) {
if (window.id === currentWindow.id) {
continue;
}
if (options.ignoreAppWindows && window.type === "app") {
if (options.ignoreAppWindows && window.type === 'app') {
continue;
}
if (options.ignorePopupWindows && window.type === "popup") {
if (options.ignorePopupWindows && window.type === 'popup') {
continue;
}

for (let tab of window.tabs) {
if (options.ignorePinnedTabs && tab.pinned) {
continue
continue;
}

if (tab.groupId !== chrome.tabGroups.TAB_GROUP_ID_NONE) {
await chrome.tabGroups.move(tab.groupId, { windowId: currentWindow.id, index: -1 })
await chrome.tabGroups.move(tab.groupId, { windowId: currentWindow.id, index: -1 });
} else {
await chrome.tabs.move(tab.id, { windowId: currentWindow.id, index: -1 })
await chrome.tabs.move(tab.id, { windowId: currentWindow.id, index: -1 });
}

if (tab.pinned == true) {
await chrome.tabs.update(tab.id, { pinned: true })
await chrome.tabs.update(tab.id, { pinned: true });
}
}
}
}

async function sortTabs() {
let options = await getOptions()
let currentWindow = await chrome.windows.getCurrent({ populate: true })
let tabs = currentWindow.tabs
let options = await getOptions();
let currentWindow = await chrome.windows.getCurrent({ populate: true });
let tabs = currentWindow.tabs;
if (options.ignorePinnedTabs) {
tabs = tabs.filter(tab => !tab.pinned)
tabs = tabs.filter((tab) => !tab.pinned);
}

tabs.sort(function (a, b) {
if (a.groupId < b.groupId) {
return -1
return -1;
} else if (a.groupId > b.groupId) {
return 1
return 1;
} else {
if (a.url < b.url) {
return -1
return -1;
} else if (a.url > b.url) {
return 1
return 1;
} else {
return 0
return 0;
}
}
})
});

for (let tab of tabs) {
if (tab.groupId !== chrome.tabGroups.TAB_GROUP_ID_NONE) {
await chrome.tabGroups.move(tab.groupId, { windowId: currentWindow.id, index: -1 })
await chrome.tabGroups.move(tab.groupId, { windowId: currentWindow.id, index: -1 });
} else {
await chrome.tabs.move(tab.id, { windowId: currentWindow.id, index: -1 })
await chrome.tabs.move(tab.id, { windowId: currentWindow.id, index: -1 });
}

if (tab.pinned == true) {
await chrome.tabs.update(tab.id, { pinned: true })
await chrome.tabs.update(tab.id, { pinned: true });
}
}
}

chrome.contextMenus.onClicked.addListener(event => {
baseAction(event.menuItemId)
async function moveTabToNewWindowAction() {
let [currentTab] = await chrome.tabs.query({ active: true, currentWindow: true });

if (!currentTab) {
console.warn('No active tab found.');
return;
}

let newWindow = await chrome.windows.create({ tabId: currentTab.id });

if (newWindow) {
console.log(`Moved tab ${currentTab.id} to new window ${newWindow.id}`);
}
}

chrome.contextMenus.onClicked.addListener((event) => {
switch (event.menuItemId) {
case "move_tab_to_new_window":
moveTabToNewWindowAction();
break;
default:
baseAction(event.menuItemId);
}
});

chrome.contextMenus.onClicked.addListener((event) => {
baseAction(event.menuItemId);
});

chrome.action.onClicked.addListener(async event => {
let options = await getOptions()
baseAction(options.defaultAction)
})
chrome.action.onClicked.addListener(async (event) => {
let options = await getOptions();
baseAction(options.defaultAction);
});

chrome.runtime.onInstalled.addListener(() => {
for (action of ALL_ACTIONS) {
if (action === DO_NOTHING) {
continue
continue;
}
chrome.contextMenus.create({
"title": action.title,
"id": action.id,
contexts: ["action"],
title: action.title,
id: action.id,
contexts: ['action'],
});
}

getOptions().then(options => {
chrome.action.setPopup({ popup: options.showDefaultActionPopup ? "popup.html" : "" })
})
})
getOptions().then((options) => {
chrome.action.setPopup({ popup: options.showDefaultActionPopup ? 'popup.html' : '' });
});
});
Loading