diff --git a/README.md b/README.md index 545a52a..02e112c 100644 --- a/README.md +++ b/README.md @@ -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| \ No newline at end of file diff --git a/background.js b/background.js index 669af04..0dbc1ec 100644 --- a/background.js +++ b/background.js @@ -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({ @@ -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' : '' }); + }); +}); diff --git a/manifest.json b/manifest.json index 9b0a8d6..466db67 100644 --- a/manifest.json +++ b/manifest.json @@ -1,31 +1,56 @@ { - "background": { - "service_worker": "background.js" - }, - "action": { - "default_icon": { - "128": "icons/128.png", - "48": "icons/48.png", - "16": "icons/16.png" - }, - "default_title": "Organize Windows and Tabs" - }, - "homepage_url": "https://github.com/fthdgn/chrome_organize_windows_tabs/", - "description": "Organize Windows and Tabs", - "icons": { + "background": { + "service_worker": "background.js" + }, + "action": { + "default_icon": { "128": "icons/128.png", "48": "icons/48.png", "16": "icons/16.png" - }, - "manifest_version": 3, - "name": "Organize Windows and Tabs", - "offline_enabled": true, - "options_page": "options.html", - "permissions": [ - "tabs", - "contextMenus", - "storage", - "tabGroups" - ], - "version": "1.4.1" -} \ No newline at end of file + }, + "default_title": "Organize Windows and Tabs" + }, + "homepage_url": "https://github.com/fthdgn/chrome_organize_windows_tabs/", + "description": "Organize Windows and Tabs", + "icons": { + "128": "icons/128.png", + "48": "icons/48.png", + "16": "icons/16.png" + }, + "manifest_version": 3, + "name": "Organize Windows and Tabs", + "offline_enabled": true, + "options_page": "options.html", + "permissions": ["tabs", "contextMenus", "storage", "tabGroups"], + "version": "1.4.1", + "commands": { + "merge-windows-and-sort-tabs": { + "suggested_key": { + "default": "Ctrl+Shift+1", + "mac": "Command+Shift+1" + }, + "description": "Merge all windows and sort tabs" + }, + "merge-windows": { + "suggested_key": { + "default": "Ctrl+Shift+0", + "mac": "Command+Shift+0" + }, + "description": "Merge all windows" + }, + "sort-tabs": { + "suggested_key": { + "default": "Ctrl+Shift+S", + "mac": "Command+Shift+S" + }, + "description": "Sort tabs" + }, + "move-tab-to-new-window": { + "suggested_key": { + "default": "Ctrl+Shift+9", + "mac": "Command+Shift+9" + }, + "description": "Move the current tab to a new window" + } + } +}