Initial commit

This commit is contained in:
Marcel Kapfer 2024-07-28 23:22:45 +02:00
commit 3454be6cf7
Signed by: mmk2410
GPG key ID: CADE6F0C09F21B09
12 changed files with 302 additions and 0 deletions

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
*.xpi

BIN
icon.afdesign Normal file

Binary file not shown.

8
icon.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 147 KiB

30
manifest.json Normal file
View file

@ -0,0 +1,30 @@
{
"background": {
"scripts": ["scripts/background.js"],
"type": "module"
},
"browser_specific_settings": {
"gecko": {
"id": "tb.spaceapper@mmk2410.org",
"strict_min_version": "115.0"
}
},
"description": "Add webpages to the spaces toolbar.",
"developer": {
"name": "Marcel Kapfer",
"url": "https://mmk2410.org"
},
"host_permissions": ["*://*/*"],
"icons": {
"48": "icon.svg",
"96": "icon.svg"
},
"manifest_version": 3,
"name": "SpaceApper",
"options_ui": {
"page": "options/options.html",
"browser_style": false
},
"permissions": ["storage", "webRequest", "webRequestBlocking"],
"version": "0.1.0"
}

64
options/options.css Normal file
View file

@ -0,0 +1,64 @@
html {
font-family: sans-serif;
}
button {
background-color: #e7e7e7;
border-radius: 5px;
border: none;
padding: 5px 10px;
box-shadow: none;
}
.btn-primary {
color: #fff;
background-color: #7a01d0;
}
#spaces {
width: 100%;
border-collapse: collapse;
}
#spaces td {
border-bottom: 1px solid #999;
border-top: 1px solid #999;
border-left: 1px solid #ddd;
border-right: 1px solid #ddd;
}
#spaces td:first-of-type {
border-left: none;
}
#spaces td:last-of-type {
border-right: none;
}
#spaces thead td {
font-weight: bold;
padding: 10px;
}
#spaces tbody input {
border: none;
height: 100%;
width: 100%;
font-size: inherit;
padding: 10px;
box-sizing: border-box;
}
#spaces tbody button {
margin: 10px;
}
#actions {
width: 100%;
margin: 10px 0;
text-align: right;
}
#actions button:first-of-type {
margin-right: 5px;
}

31
options/options.html Normal file
View file

@ -0,0 +1,31 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<link rel="stylesheet" href="options.css" />
</head>
<body>
<form>
<table id="spaces">
<thead>
<tr>
<td>Name</td>
<td>URL</td>
<td>Icon URL</td>
<td></td>
</tr>
</thead>
<tbody></tbody>
</table>
<div id="actions">
<button id="add" type="button">Add</button>
<button type="submit" class="btn-primary">Save</button>
</div>
</form>
<script type="module" src="options.js"></script>
</body>
</html>

81
options/options.js Normal file
View file

@ -0,0 +1,81 @@
import { createSpaces, removeSpaces, hashSpaceName } from "../scripts/spaces.mjs";
import { getApps, storeApps } from "../scripts/storage.mjs";
function buildInputRow(name, value) {
const td = document.createElement("td");
const input = document.createElement("input");
input.setAttribute("type", "text");
input.setAttribute("name", name);
input.setAttribute("value", value);
td.appendChild(input);
return td;
}
function buildSpaceElement(app) {
const tr = document.createElement("tr");
tr.className = "space";
tr.appendChild(buildInputRow("title", app.title));
tr.appendChild(buildInputRow("url", app.url));
tr.appendChild(buildInputRow("icon", app.icon));
const removeTd = document.createElement("td");
const removeButton = document.createElement("button");
removeButton.setAttribute("type", "button");
removeButton.addEventListener("click", removeSpace);
removeButton.textContent = "Remove";
removeTd.appendChild(removeButton);
tr.appendChild(removeTd);
return tr;
}
async function setupPage() {
const apps = await getApps();
const spaces = document.querySelector("#spaces > tbody");
apps.forEach((app) => {
spaces.appendChild(buildSpaceElement(app));
});
}
function addSpace() {
const spaces = document.querySelector("#spaces > tbody");
const newApp = {
title: "",
url: "",
icon: "",
};
spaces.appendChild(buildSpaceElement(newApp));
}
function removeSpace(e) {
e.target.parentElement.parentElement.remove();
}
async function saveChanges(e) {
await e.preventDefault();
const apps = [];
const spaces = Array.from(document.getElementsByClassName("space"));
spaces.forEach((space) => {
const app = {
name: hashSpaceName(space.querySelector("input[name='url']").value),
title: space.querySelector("input[name='title']").value,
url: space.querySelector("input[name='url']").value,
icon: space.querySelector("input[name='icon']").value,
};
apps.push(app);
});
await removeSpaces();
await storeApps(apps);
await createSpaces();
}
document.addEventListener("DOMContentLoaded", setupPage);
document.getElementById("add").addEventListener("click", addSpace);
document.querySelector("form").addEventListener("submit", saveChanges);

7
readme.md Normal file
View file

@ -0,0 +1,7 @@
# SpaceApper
**A Thunderbird add-on**
Add webpages to the Thunderbird spaces toolbar.
*Attention: The author has no experience in creating a add-on for Thunderbird. Therefore, the add-on should not be considered stable.*

3
scripts/background.js Normal file
View file

@ -0,0 +1,3 @@
import { createSpaces } from "./spaces.mjs";
createSpaces();

11
scripts/listeners.mjs Normal file
View file

@ -0,0 +1,11 @@
export { changeUserAgent };
function changeUserAgent(details) {
for (const header of details.requestHeaders) {
if (header.name.toLowerCase() === "user-agent") {
header.value = "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/115.0";
break;
}
}
return { requestHeaders: details.requestHeaders };
}

49
scripts/spaces.mjs Normal file
View file

@ -0,0 +1,49 @@
import { changeUserAgent } from "./listeners.mjs";
import { getApps, storeApps } from "./storage.mjs";
export { createSpaces, removeSpaces, hashSpaceName };
function hashSpaceName(url) {
return btoa(url).replaceAll("=", "");
}
async function createSpaces() {
const apps = await getApps();
if (apps.length === 0) {
return;
}
const urls = [];
apps.forEach(async (app) => {
urls.push(app.url);
await browser.spaces.create(app.name, app.url, {
title: app.title,
defaultIcons: app.icon,
});
});
const matchUrls = urls.map((url) => {
return url.replace(/^(https?:\/\/).*((?<=[\/\.])[^\/\.?]*\.[^\/\.?]*)[\/?]?.*$/g, "$1*.$2/*");
});
if (browser.webRequest.onBeforeSendHeaders.hasListener(changeUserAgent)) {
browser.webRequest.onBeforeSendHeaders.removeListener(changeUserAgent);
}
browser.webRequest.onBeforeSendHeaders.addListener(changeUserAgent, { urls: matchUrls }, [
"requestHeaders",
"blocking",
]);
}
async function removeSpaces() {
const spaces = await browser.spaces.query({ isSelfOwned: true });
spaces.forEach(async (space) => {
await browser.spaces.remove(space.id);
});
await storeApps([]);
}

17
scripts/storage.mjs Normal file
View file

@ -0,0 +1,17 @@
export { storeApps, getApps };
async function storeApps(apps) {
await browser.storage.sync.set({
apps: apps,
});
}
async function getApps() {
const res = await browser.storage.sync.get("apps");
if (res.apps === undefined) {
return [];
}
return res.apps;
}