Skip to content
This repository was archived by the owner on Nov 11, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 0 additions & 76 deletions background.js

This file was deleted.

24 changes: 11 additions & 13 deletions manifest.json
Original file line number Diff line number Diff line change
@@ -1,13 +1,8 @@
{
"manifest_version": 2,
"manifest_version": 3,
"name": "Trektor",
"description": "Browser-Extension zum automatischen Anlegen von Toggl tracking tasks",
"version": "0.0.11",
"browser_specific_settings": {
"gecko": {
"id": "trektor@aboutsource.net"
}
},
"icons": {
"64": "icons/64.png"
},
Expand All @@ -17,26 +12,29 @@
"https://trello.com/*"
],
"js": [
"vendor/browser-polyfill.js",
"content_script.js"
"scripts/trektor.js",
"scripts/content_script.js"
],
"css": [
"content_style.css"
]
}
],
"background": {
"service_worker": "scripts/chromium.js",
"scripts": [
"vendor/browser-polyfill.js",
"trektor.js",
"background.js"
"scripts/trektor.js",
"scripts/background.js",
"scripts/firefox.js"
]
},
"permissions": [
"https://api.trello.com/*",
"https://api.track.toggl.com/*",
"storage"
],
"host_permissions": [
"https://api.trello.com/*",
"https://api.track.toggl.com/*"
],
"options_ui": {
"page": "options/index.html"
}
Expand Down
2 changes: 1 addition & 1 deletion options/script.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@ document.querySelectorAll("input").forEach((field) => {
});

browser.storage.local.get(field.name).then(({ [field.name]: value }) => {
field.value = (value === undefined) ? '' : value;
field.value = (value === undefined) ? "" : value;
});
});
84 changes: 84 additions & 0 deletions scripts/background.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
class BackgroundWorker {

constructor() {
}

run() {
trektor.browser.runtime.onMessage.addListener(async (msg) => {
switch (msg.action) {
case "track":
await this.track(...msg.args);
return;
case "addTask":
await this.addTask(...msg.args);
return;
default:
throw new Error(`unknown action: ${msg.action}`);
}
});
}

async track(cardId) {
const task = await this.addTask(cardId);
const card = await trektor.trelloGateway.getCard(cardId);
const cardName = this.stripStoryPointsAndTaskToken(card.name);
const response = await trektor.togglGateway.startTimeEntry(task.id, cardName);
return response.data;
}

async addTask(cardId) {
const card = await trektor.trelloGateway.getCard(cardId);

const taskPrefixes = card.labels
.map((label) => label.name.match(/(?<=#)[a-z0-9]+$/)?.[0])
.filter((prefix) => prefix !== undefined);

if (taskPrefixes.length === 0) {
throw new Error("Card has no valid project labels.");
}
if (taskPrefixes.length > 1) {
throw new Error("Card has multiple project labels.");
}
const taskPrefix = taskPrefixes[0];

let taskName = card.name.match(/(?<=#)[A-Za-z0-9_-]+/)?.[0];

if (taskName === undefined) {
taskName = `${taskPrefix}_${card.idShort}`;

await trektor.trelloGateway.updateCard(card.id, {
name: `${card.name} #${taskName}`,
});
}

const workspaces = await trektor.togglGateway.getWorkspaces();

if (workspaces.length === 0) {
throw new Error("Could not find any toggl workspaces.");
}
if (workspaces.length > 1) {
throw new Error("Found multiple toggl workspaces. Not sure how to deal with that...");
}
const allProjects = await trektor.togglGateway.getProjects(workspaces[0].id);
const projects = allProjects.filter((project) => project.name.endsWith(`(${taskPrefix})`));

if (projects.length === 0) {
throw new Error("Could not find any matching toggl project.");
}
if (projects.length > 1) {
throw new Error("Found multiple matching toggl projects. Not sure how to deal with that...");
}
const tasks = await trektor.togglGateway.getTasks(projects[0].id);
const task = tasks.find((task) => task.name === taskName);
if (task !== undefined) return task;

const response = await trektor.togglGateway.createTask(projects[0].id, taskName)
return response.data;
}

stripStoryPointsAndTaskToken(cardName) {
return cardName
.replace(/^(\s*\(\d+\))?\s*/, "") // story points, e.g. (3)
.replace(/\s*#[a-z0-9_]+\s*$/, ""); // task token, e.g. #orga_5417
}
}
3 changes: 3 additions & 0 deletions scripts/chromium.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
importScripts('trektor.js', 'background.js');

new BackgroundWorker().run()
10 changes: 3 additions & 7 deletions content_script.js → scripts/content_script.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ async function addButton() {
trackButtonIcon.classList.add("trektor-state-loading");

try {
await browser.runtime.sendMessage({
await trektor.browser.runtime.sendMessage({
action: "track",
args: [window.location.pathname.split("/", 3)[2]],
});
Expand All @@ -49,7 +49,7 @@ async function addButton() {

addButton.addEventListener("click", async () => {
try {
await browser.runtime.sendMessage({
await trektor.browser.runtime.sendMessage({
action: "addTask",
args: [window.location.pathname.split("/", 3)[2]],
});
Expand All @@ -71,7 +71,7 @@ function awaitSelector(selector, timeout) {
resolve(element);
window.clearInterval(interval);
} else if (timeout < 0) {
reject(new Error('timeout'));
reject(new Error("timeout"));
window.clearInterval(interval);
}
timeout -= 100;
Expand All @@ -82,7 +82,3 @@ function awaitSelector(selector, timeout) {
window.addEventListener("pushstate", () => {
if (window.location.pathname.startsWith("/c/")) addButton();
});

window.addEventListener('load', () => {
if (window.location.pathname.startsWith("/c/")) addButton();
});
1 change: 1 addition & 0 deletions scripts/firefox.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
new BackgroundWorker().run()
10 changes: 8 additions & 2 deletions trektor.js → scripts/trektor.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
if (typeof browser == "undefined") {
globalThis.browser = chrome
}

class TrelloGateway {
static ENDPOINT = "https://api.trello.com/1";
static API_KEY = "2379d540412e417f6f0696c1397f38a6";
Expand Down Expand Up @@ -95,7 +99,9 @@ class TogglGateway {
}
}

window.trektor = {

const trektor = {
trelloGateway: new TrelloGateway(browser.storage.local),
togglGateway: new TogglGateway(browser.storage.local),
};
browser: browser,
}
Loading