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