Initial commit
This commit is contained in:
commit
0c80bcfb58
8 changed files with 288 additions and 0 deletions
9
build-package.sh
Executable file
9
build-package.sh
Executable file
|
@ -0,0 +1,9 @@
|
||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
set -ex;
|
||||||
|
|
||||||
|
pushd src/
|
||||||
|
|
||||||
|
zip ../tabunny.zip *
|
||||||
|
|
||||||
|
popd
|
18
src/manifest.json
Normal file
18
src/manifest.json
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
{
|
||||||
|
"manifest_version": 2,
|
||||||
|
"name": "Tabunny",
|
||||||
|
"version": "0.3",
|
||||||
|
|
||||||
|
"description": "manages tabs for bunnies",
|
||||||
|
|
||||||
|
"browser_action": {
|
||||||
|
"browser_style": false,
|
||||||
|
"default_title": "Tabunny",
|
||||||
|
"default_popup": "tabunny.html"
|
||||||
|
},
|
||||||
|
|
||||||
|
"permissions": [
|
||||||
|
"tabs",
|
||||||
|
"storage"
|
||||||
|
]
|
||||||
|
}
|
68
src/script.js
Normal file
68
src/script.js
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
const $ = (q) => document.querySelector(q);
|
||||||
|
const sessionName = $("#session-name");
|
||||||
|
const saveSession = $("#save-session");
|
||||||
|
const saveQuitSession = $("#save-quit-session");
|
||||||
|
const saveStatus = $("#save-status");
|
||||||
|
|
||||||
|
const openSessionsViewer = $("#open-sessions-viewer");
|
||||||
|
|
||||||
|
sessionName.value = new Date().toJSON();
|
||||||
|
sessionName.select();
|
||||||
|
|
||||||
|
async function doSaveSession() {
|
||||||
|
const tabs = await browser.tabs.query({});
|
||||||
|
const localStorage = browser.storage.local;
|
||||||
|
const currentSessionName = sessionName.value;
|
||||||
|
|
||||||
|
const sessionID = crypto.randomUUID();
|
||||||
|
const storedSessions = await localStorage.get("sessions");
|
||||||
|
|
||||||
|
let sessions = storedSessions["sessions"] ?? [];
|
||||||
|
sessions.push({
|
||||||
|
id: sessionID,
|
||||||
|
name: currentSessionName,
|
||||||
|
time: new Date(),
|
||||||
|
});
|
||||||
|
|
||||||
|
await localStorage.set({ sessions });
|
||||||
|
|
||||||
|
let sessionData = {
|
||||||
|
tabs: []
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const tab of tabs) {
|
||||||
|
const tabData = {
|
||||||
|
id: tab.id,
|
||||||
|
url: tab.url,
|
||||||
|
title: tab.title,
|
||||||
|
window: tab.windowId,
|
||||||
|
favicon: tab.favIconUrl,
|
||||||
|
}
|
||||||
|
sessionData["tabs"].push(tabData);
|
||||||
|
}
|
||||||
|
|
||||||
|
const dataToSave = {};
|
||||||
|
dataToSave["session." + sessionID] = sessionData;
|
||||||
|
await localStorage.set(dataToSave);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function endSession() {
|
||||||
|
const tabs = await browser.tabs.query({});
|
||||||
|
browser.tabs.create({});
|
||||||
|
for (const tab of tabs) {
|
||||||
|
await browser.tabs.remove(tab.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
saveSession.onclick = () => {
|
||||||
|
doSaveSession().then(saveStatus.innerText = "Session saved!");
|
||||||
|
};
|
||||||
|
|
||||||
|
saveQuitSession.onclick = () => {
|
||||||
|
doSaveSession().then(endSession());
|
||||||
|
}
|
||||||
|
|
||||||
|
openSessionsViewer.onclick = () => {
|
||||||
|
browser.tabs.create({url: 'sessions-browser.html'});
|
||||||
|
};
|
22
src/sessions-browser.html
Normal file
22
src/sessions-browser.html
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>Tabunny sessions</title>
|
||||||
|
<link href="style.css" rel="stylesheet" />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<main>
|
||||||
|
<h1>Tabunny sessions</h1>
|
||||||
|
|
||||||
|
<h2>Past sessions</h2>
|
||||||
|
|
||||||
|
<div id="sessions">
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
<script src="viewer.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
41
src/style.css
Normal file
41
src/style.css
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
:root {
|
||||||
|
color-scheme: light dark;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
html, body {
|
||||||
|
--background: light-dark(#eff1f5, #1e1e2e);
|
||||||
|
--text: light-dark(#4c4f69, #cdd6f4);
|
||||||
|
--link: light-dark(#8839ef, #cba6f7)
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
background: var(--background);
|
||||||
|
color: var(--text);
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
main {
|
||||||
|
width: 95%;
|
||||||
|
max-width: 50rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: var(--link);
|
||||||
|
}
|
||||||
|
|
||||||
|
img.favicon {
|
||||||
|
height: 12pt;
|
||||||
|
margin: 1pt;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
a.action-link {
|
||||||
|
margin-right: 3pt;
|
||||||
|
}
|
28
src/tabunny.html
Normal file
28
src/tabunny.html
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>Tabunny</title>
|
||||||
|
<link href="style.css" rel="stylesheet" />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<main>
|
||||||
|
<button id="open-sessions-viewer">View past sessions</button>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label for="session-name">Session name:</label>
|
||||||
|
<input autofocus type="text" id="session-name" name="session-name" placeholder="Session name" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button id="save-session">Save</button>
|
||||||
|
<button id="save-quit-session">Save and end session</button>
|
||||||
|
|
||||||
|
<output id="save-status"></output>
|
||||||
|
|
||||||
|
<script src="script.js"></script>
|
||||||
|
|
||||||
|
</main>
|
||||||
|
</body>
|
||||||
|
</html>
|
102
src/viewer.js
Normal file
102
src/viewer.js
Normal file
|
@ -0,0 +1,102 @@
|
||||||
|
const $ = (q) => document.querySelector(q);
|
||||||
|
|
||||||
|
const sessionsDisplay = $("#sessions");
|
||||||
|
|
||||||
|
const localStorage = browser.storage.local;
|
||||||
|
|
||||||
|
async function loadSessions() {
|
||||||
|
const storedSessions = await localStorage.get("sessions");
|
||||||
|
const sessions = storedSessions["sessions"] ?? [];
|
||||||
|
|
||||||
|
for (const session of sessions) {
|
||||||
|
const sessionDisplay = document.createElement("details");
|
||||||
|
const sessionSummary = document.createElement("summary");
|
||||||
|
|
||||||
|
sessionSummary.innerText = session.name;
|
||||||
|
sessionDisplay.appendChild(sessionSummary);
|
||||||
|
|
||||||
|
const deleteAction = document.createElement("a");
|
||||||
|
deleteAction.href = "#";
|
||||||
|
deleteAction.innerText = "Delete";
|
||||||
|
deleteAction.classList.add("action-link");
|
||||||
|
deleteAction.onclick = async () => {
|
||||||
|
if (!confirm("Do you really want to delete " + session.name + "?")) return;
|
||||||
|
// Let's avoid issues if the tab was left running for a while!
|
||||||
|
const currentStoredSessions = await localStorage.get("sessions");
|
||||||
|
const currentSessions = storedSessions["sessions"] ?? [];
|
||||||
|
let newSessions = currentSessions.filter(theSession => theSession !== session);
|
||||||
|
await localStorage.set({sessions: newSessions});
|
||||||
|
await localStorage.remove("session." + session.id);
|
||||||
|
sessionDisplay.remove();
|
||||||
|
};
|
||||||
|
sessionDisplay.appendChild(deleteAction);
|
||||||
|
|
||||||
|
const renameAction = document.createElement("a");
|
||||||
|
renameAction.href = "#";
|
||||||
|
renameAction.innerText = "Rename";
|
||||||
|
renameAction.classList.add("action-link");
|
||||||
|
renameAction.onclick = async () => {
|
||||||
|
// Let's avoid issues if the tab was left running for a while!
|
||||||
|
const currentStoredSessions = await localStorage.get("sessions");
|
||||||
|
const currentSessions = storedSessions["sessions"] ?? [];
|
||||||
|
const newName = prompt("Enter the new name", session.name);
|
||||||
|
let newSessions = currentSessions.map(
|
||||||
|
theSession => (theSession === session ? { ...theSession, name: newName } : theSession)
|
||||||
|
);
|
||||||
|
await localStorage.set({sessions: newSessions});
|
||||||
|
sessionSummary.innerText = newName;
|
||||||
|
};
|
||||||
|
sessionDisplay.appendChild(renameAction);
|
||||||
|
|
||||||
|
const fullSessionID = "session." + session.id;
|
||||||
|
const storedSessionData = await localStorage.get(fullSessionID);
|
||||||
|
const sessionData = storedSessionData[fullSessionID];
|
||||||
|
if (sessionData == undefined) {
|
||||||
|
const errorMessage = document.createElement("p");
|
||||||
|
errorMessage.innerText = "No session data available. Save probably failed, and data was lost :(";
|
||||||
|
sessionDisplay.appendChild(errorMessage);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const tabsData = sessionData["tabs"];
|
||||||
|
if (tabsData == undefined) {
|
||||||
|
const errorMessage = document.createElement("p");
|
||||||
|
errorMessage.innerText = "No tabs data available. Save probably failed, and data was lost :(";
|
||||||
|
sessionDisplay.appendChild(errorMessage);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let tabsListCSV = "session,window,name,url,favicon\n";
|
||||||
|
|
||||||
|
const tabsList = document.createElement("ul");
|
||||||
|
for (const tab of tabsData) {
|
||||||
|
const tabDisplay = document.createElement("li");
|
||||||
|
tabDisplay.innerText = "Window " + tab.window + ": ";
|
||||||
|
if (tab.favicon) {
|
||||||
|
const faviconDisplay = document.createElement("img");
|
||||||
|
faviconDisplay.src = tab.favicon;
|
||||||
|
faviconDisplay.classList.add("favicon");
|
||||||
|
tabDisplay.appendChild(faviconDisplay);
|
||||||
|
}
|
||||||
|
const tabLink = document.createElement("a");
|
||||||
|
tabLink.href = tab.url;
|
||||||
|
tabLink.innerText = tab.title;
|
||||||
|
tabDisplay.appendChild(tabLink);
|
||||||
|
tabsList.appendChild(tabDisplay);
|
||||||
|
|
||||||
|
tabsListCSV += `"${session.name}",${tab.window},"${tab.title}",${tab.url},${tab.favicon}\n`;
|
||||||
|
}
|
||||||
|
sessionDisplay.appendChild(tabsList);
|
||||||
|
|
||||||
|
const exportAction = document.createElement("a");
|
||||||
|
exportAction.href = "data:text/csv;charset=utf-8," + encodeURIComponent(tabsListCSV);
|
||||||
|
exportAction.innerText = "Export (CSV)";
|
||||||
|
exportAction.classList.add("action-link");
|
||||||
|
exportAction.download = session.name + ".csv";
|
||||||
|
sessionDisplay.appendChild(exportAction);
|
||||||
|
|
||||||
|
sessionsDisplay.appendChild(sessionDisplay);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
loadSessions();
|
BIN
tabunny.zip
Normal file
BIN
tabunny.zip
Normal file
Binary file not shown.
Loading…
Reference in a new issue