mirror of
https://github.com/He4eT/tabswiper.git
synced 2026-05-04 16:47:23 +00:00
Initial commit
This commit is contained in:
parent
b47ce482b8
commit
9306068d2a
7 changed files with 478 additions and 0 deletions
7
.editorconfig
Normal file
7
.editorconfig
Normal file
|
|
@ -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
|
||||||
197
api/tabs/tabs.js
Normal file
197
api/tabs/tabs.js
Normal file
|
|
@ -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}`);
|
||||||
|
});
|
||||||
21
manifest.json
Normal file
21
manifest.json
Normal file
|
|
@ -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"
|
||||||
|
}
|
||||||
57
popup/css/popup.css
Normal file
57
popup/css/popup.css
Normal file
|
|
@ -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);
|
||||||
|
}
|
||||||
53
popup/tabswiper/tabswiper.css
Normal file
53
popup/tabswiper/tabswiper.css
Normal file
|
|
@ -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;
|
||||||
|
}
|
||||||
58
popup/tabswiper/tabswiper.html
Normal file
58
popup/tabswiper/tabswiper.html
Normal file
|
|
@ -0,0 +1,58 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html lang='en'>
|
||||||
|
<head>
|
||||||
|
<meta charset='UTF-8'>
|
||||||
|
<title>Tabswiper</title>
|
||||||
|
<link rel='stylesheet' href='../css/popup.css' />
|
||||||
|
<link rel='stylesheet' href='./tabswiper.css' />
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<main class='popup'>
|
||||||
|
<article class='tabswiper'>
|
||||||
|
<header>
|
||||||
|
<section class='tabCounter'>
|
||||||
|
Tab 1 of 20
|
||||||
|
</section>
|
||||||
|
<a
|
||||||
|
class='infoLink'
|
||||||
|
target='_blank'
|
||||||
|
href='#'
|
||||||
|
>
|
||||||
|
Info
|
||||||
|
</a>
|
||||||
|
</header>
|
||||||
|
<section class='currentTab'>
|
||||||
|
<section id='titleTab' class='title'>
|
||||||
|
Some title here
|
||||||
|
</section>
|
||||||
|
<a
|
||||||
|
id='linkTab'
|
||||||
|
class='url'
|
||||||
|
href='https://some.url/here'
|
||||||
|
>
|
||||||
|
https://some.url/here
|
||||||
|
</a>
|
||||||
|
</section>
|
||||||
|
<footer>
|
||||||
|
<button
|
||||||
|
id='buttonClose'
|
||||||
|
class='actionButton close'
|
||||||
|
title='Close this tab. Shortcuts: [J], [Left Arrow]'
|
||||||
|
>
|
||||||
|
Close
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
id='buttonKeep'
|
||||||
|
class='actionButton keep'
|
||||||
|
title='Keep this tab. Shortcuts: [K], [Right Arrow]'
|
||||||
|
>
|
||||||
|
Keep
|
||||||
|
</button>
|
||||||
|
</footer>
|
||||||
|
</article>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<script type='module' src='./tabswiper.js'></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
85
popup/tabswiper/tabswiper.js
Normal file
85
popup/tabswiper/tabswiper.js
Normal file
|
|
@ -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()
|
||||||
Loading…
Add table
Add a link
Reference in a new issue