diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..c257e39 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,7 @@ +[*] +indent_style = space +indent_size = 2 +end_of_line = lf +trim_trailing_whitespace = true +insert_final_newline = true +max_line_length = 80 diff --git a/api/tabs/tabs.js b/api/tabs/tabs.js new file mode 100644 index 0000000..0546b16 --- /dev/null +++ b/api/tabs/tabs.js @@ -0,0 +1,197 @@ +export const getWindowTabs = () => { + return browser.tabs.query({currentWindow: true}) +} + +export const removeTab = (tab) => { + browser.tabs.remove(tab.id) +} + +/** + * listTabs to switch to + */ +function listTabs() { + getCurrentWindowTabs().then((tabs) => { + let tabsList = document.getElementById('tabs-list'); + let currentTabs = document.createDocumentFragment(); + let limit = 5; + let counter = 0; + + tabsList.textContent = ''; + + for (let tab of tabs) { + if (!tab.active && counter <= limit) { + let tabLink = document.createElement('a'); + + tabLink.textContent = tab.title || tab.id; + tabLink.setAttribute('href', tab.id); + tabLink.classList.add('switch-tabs'); + currentTabs.appendChild(tabLink); + } + + counter += 1; + } + + tabsList.appendChild(currentTabs); + }); +} + +document.addEventListener("DOMContentLoaded", listTabs); + +function getCurrentWindowTabs() { + return browser.tabs.query({currentWindow: true}); +} + +document.addEventListener("click", (e) => { + function callOnActiveTab(callback) { + getCurrentWindowTabs().then((tabs) => { + for (let tab of tabs) { + if (tab.active) { + callback(tab, tabs); + } + } + }); +} + + if (e.target.id === "tabs-move-beginning") { + callOnActiveTab((tab, tabs) => { + let index = 0; + if (!tab.pinned) { + index = firstUnpinnedTab(tabs); + } + console.log(`moving ${tab.id} to ${index}`) + browser.tabs.move([tab.id], {index}); + }); + } + + if (e.target.id === "tabs-move-end") { + callOnActiveTab((tab, tabs) => { + let index = -1; + if (tab.pinned) { + let lastPinnedTab = Math.max(0, firstUnpinnedTab(tabs) - 1); + index = lastPinnedTab; + } + browser.tabs.move([tab.id], {index}); + }); + } + + else if (e.target.id === "tabs-duplicate") { + callOnActiveTab((tab) => { + browser.tabs.duplicate(tab.id); + }); + } + + else if (e.target.id === "tabs-reload") { + callOnActiveTab((tab) => { + browser.tabs.reload(tab.id); + }); + } + + else if (e.target.id === "tabs-remove") { + callOnActiveTab((tab) => { + browser.tabs.remove(tab.id); + }); + } + + else if (e.target.id === "tabs-create") { + browser.tabs.create({url: "https://developer.mozilla.org/en-US/Add-ons/WebExtensions"}); + } + + else if (e.target.id === "tabs-create-reader") { + browser.tabs.create({url: "https://developer.mozilla.org/en-US/Add-ons/WebExtensions", openInReaderMode: true}); + } + + else if (e.target.id === "tabs-alertinfo") { + callOnActiveTab((tab) => { + let props = ""; + for (let item in tab) { + props += `${ item } = ${ tab[item] } \n`; + } + alert(props); + }); + } + + else if (e.target.id === "tabs-add-zoom") { + callOnActiveTab((tab) => { + let gettingZoom = browser.tabs.getZoom(tab.id); + gettingZoom.then((zoomFactor) => { + //the maximum zoomFactor is 5, it can't go higher + if (zoomFactor >= MAX_ZOOM) { + alert("Tab zoom factor is already at max!"); + } else { + let newZoomFactor = zoomFactor + ZOOM_INCREMENT; + //if the newZoomFactor is set to higher than the max accepted + //it won't change, and will never alert that it's at maximum + newZoomFactor = newZoomFactor > MAX_ZOOM ? MAX_ZOOM : newZoomFactor; + browser.tabs.setZoom(tab.id, newZoomFactor); + } + }); + }); + } + + else if (e.target.id === "tabs-decrease-zoom") { + callOnActiveTab((tab) => { + let gettingZoom = browser.tabs.getZoom(tab.id); + gettingZoom.then((zoomFactor) => { + //the minimum zoomFactor is 0.3, it can't go lower + if (zoomFactor <= MIN_ZOOM) { + alert("Tab zoom factor is already at minimum!"); + } else { + let newZoomFactor = zoomFactor - ZOOM_INCREMENT; + //if the newZoomFactor is set to lower than the min accepted + //it won't change, and will never alert that it's at minimum + newZoomFactor = newZoomFactor < MIN_ZOOM ? MIN_ZOOM : newZoomFactor; + browser.tabs.setZoom(tab.id, newZoomFactor); + } + }); + }); + } + + else if (e.target.id === "tabs-default-zoom") { + callOnActiveTab((tab) => { + let gettingZoom = browser.tabs.getZoom(tab.id); + gettingZoom.then((zoomFactor) => { + if (zoomFactor == DEFAULT_ZOOM) { + alert("Tab zoom is already at the default zoom factor"); + } else { + browser.tabs.setZoom(tab.id, DEFAULT_ZOOM); + } + }); + }); + } + + else if (e.target.classList.contains('switch-tabs')) { + let tabId = +e.target.getAttribute('href'); + + browser.tabs.query({ + currentWindow: true + }).then((tabs) => { + for (let tab of tabs) { + if (tab.id === tabId) { + browser.tabs.update(tabId, { + active: true + }); + } + } + }); + } + + e.preventDefault(); +}); + +//onRemoved listener. fired when tab is removed +browser.tabs.onRemoved.addListener((tabId, removeInfo) => { + console.log(`The tab with id: ${tabId}, is closing`); + + if(removeInfo.isWindowClosing) { + console.log(`Its window is also closing.`); + } else { + console.log(`Its window is not closing`); + } +}); + +//onMoved listener. fired when tab is moved into the same window +browser.tabs.onMoved.addListener((tabId, moveInfo) => { + let startIndex = moveInfo.fromIndex; + let endIndex = moveInfo.toIndex; + console.log(`Tab with id: ${tabId} moved from index: ${startIndex} to index: ${endIndex}`); +}); diff --git a/manifest.json b/manifest.json new file mode 100644 index 0000000..381f267 --- /dev/null +++ b/manifest.json @@ -0,0 +1,21 @@ +{ + "action": { + "default_title": "Tabswiper", + "default_popup": "popup/tabswiper/tabswiper.html" + }, + "commands": { + "_execute_action": { + "suggested_key": { + "default": "Shift+F2" + } + } + }, + "description": "Clear the list of your tabs from outdated and unnecessary ones", + "homepage_url": "https://github.com/He4eT/tabswiper", + "manifest_version": 3, + "name": "Tabswiper", + "permissions": [ + "tabs" + ], + "version": "1.0" +} diff --git a/popup/css/popup.css b/popup/css/popup.css new file mode 100644 index 0000000..ce278fa --- /dev/null +++ b/popup/css/popup.css @@ -0,0 +1,57 @@ +html, body { + font-family: sans; + margin: 0; + padding: 0; +} + +.popup { + --color-bg: #eeeeee; + --color-text: #333333; + --color-accent: #666666; + + --step: 8px; +} + +@media (prefers-color-scheme: dark) { + .popup { + --color-bg: darkgray; + --color-text: white; + --color-accent: blue; + } +} + +/* Controls */ + +*:focus-visible { + outline-color: var(--color-accent); + outline-offset: 2px; + outline-style: solid; + outline-width: 1px; +} + +/* Button */ + +button { + background: var(--color-bg); + + border-radius: 0; + border: 1px solid var(--color-text); + + color: var(--color-text); + + cursor: pointer; + font: inherit; + + padding: var(--step); +} + +button:active { + border-color: var(--color-accent); + color: var(--color-accent); +} + +/* Link */ + +a { + color: var(--color-accent); +} diff --git a/popup/tabswiper/tabswiper.css b/popup/tabswiper/tabswiper.css new file mode 100644 index 0000000..a9deb68 --- /dev/null +++ b/popup/tabswiper/tabswiper.css @@ -0,0 +1,53 @@ +.tabswiper { + background-color: var(--color-bg); + border: 1px solid var(--color-accent); + color: var(--color-text); + width: 640px; +} + +/* Header */ + +.tabswiper header { + padding: var(--step); + border-block-end: 1px solid var(--color-accent); + + display: flex; + flex-direction: row; + justify-content: space-between; + aligh-items: center; +} + +/* Current Tab */ + +.tabswiper .currentTab { + padding: calc(1 * var(--step)) var(--step); + cursor: pointer; + word-wrap: break-word; + /* line-height: 1.5; */ +} + +.tabswiper .currentTab .title { + /* font-size: 1.2em; */ + margin-block-end: calc(1 * var(--step)); + font-weight: bold; +} + +.tabswiper .currentTab .url { + /* font-size: 0.8em; */ + color: var(--color-accent); +} + +/* Footer */ + +.tabswiper footer { + padding: var(--step); + border-block-start: 1px solid var(--color-accent); + + display: flex; + flex-direction: row; + gap: var(--step); +} + +.tabswiper footer .actionButton { + flex: 1 1 auto; +} diff --git a/popup/tabswiper/tabswiper.html b/popup/tabswiper/tabswiper.html new file mode 100644 index 0000000..7367704 --- /dev/null +++ b/popup/tabswiper/tabswiper.html @@ -0,0 +1,58 @@ + + + + + Tabswiper + + + + + +
+
+
+
+ Tab 1 of 20 +
+ + Info + +
+
+
+ Some title here +
+ + https://some.url/here + +
+ +
+
+ + + + diff --git a/popup/tabswiper/tabswiper.js b/popup/tabswiper/tabswiper.js new file mode 100644 index 0000000..28058a9 --- /dev/null +++ b/popup/tabswiper/tabswiper.js @@ -0,0 +1,85 @@ +/* */ + +const state = { + tabs: [], + skipped: [], + currentTab: null, +} + +const updateState = () => + updateTabs() + .then(updateCurrent) + .then(updateInterface) + +const updateTabs = () => + browser.tabs.query({currentWindow: true}) + .then((tabs) => tabs.reverse()) + .then((tabs) => void (state.tabs = tabs)) + +const updateCurrent = () => { + const filteredTabs = state.tabs + .filter(({ id }) => state.skipped.includes(id) === false) + state.currentTab = filteredTabs[0] ?? null +} + +const keepTab = (tab) => { + state.skipped.push(tab.id) + updateState() +} + +const goToTab = (tab) => { + browser.tabs.update(tab.id, { active: true }) + updateState() +} + +const closeTab = (tab) => { + browser.tabs.remove(tab.id) + updateState() +} + +/* */ + +const setDOMListeners = () => { + const pairs = [ + ['buttonClose', closeTab], + ['buttonKeep', keepTab], + ['linkTab', goToTab], + ] + + pairs.forEach(([elementId, handler]) => { + document.getElementById(elementId).addEventListener('click', (e) => { + console.log('Element:', elementId, 'Handler:', handler) + e.preventDefault() + handler(state.currentTab) + }) + }) +} + +const updateInterface = () => { + console.log(state) + + if (state.currentTab === null) { + location.reload() + return + } + + const items = [ + ['titleTab', 'textContent', state.currentTab.title], + ['linkTab', 'textContent', state.currentTab.url], + ['linkTab', 'href', state.currentTab.url], + ] + + items.forEach(([elementId, property, value]) => { + document.getElementById(elementId)[property] = value + }) +} + +/* */ + +const init = () => + updateState() + .then(setDOMListeners) + // .then(setKeyboardListeners) + // .then(setBrowserListeners) + +init()