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 6 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.

22 changes: 10 additions & 12 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 @@ -18,25 +13,28 @@
],
"js": [
"vendor/browser-polyfill.js",
"content_script.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
89 changes: 89 additions & 0 deletions scripts/background.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
if (typeof browser == "undefined") {
globalThis.browser = chrome
}

class BackgroundScript {

static trektor;

static init(trektor) {
BackgroundScript.trektor = trektor

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

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

static async addTask(cardId) {
const card = await BackgroundScript.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}_${card.shortLink}`;

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

const workspaces = await BackgroundScript.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 BackgroundScript.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 BackgroundScript.trektor.togglGateway.getTasks(projects[0].id);
const task = tasks.find((task) => task.name === taskName);
if (task !== undefined) return task;

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

static 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('background.js', 'trektor.js');

BackgroundScript.init(getTrektor());
12 changes: 6 additions & 6 deletions content_script.js → scripts/content_script.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,27 +34,31 @@ async function addButton() {
trackButtonIcon.classList.add("trektor-state-loading");

try {
console.log("start tracking card")
await browser.runtime.sendMessage({
action: "track",
action: 'track',
args: [window.location.pathname.split("/", 3)[2]],
});
trackButtonIcon.classList.replace("icon-clock", "icon-check-circle");
window.setTimeout(() => trackButtonIcon.classList.replace("icon-check-circle", "icon-clock"), 2000);
} catch (err) {
window.alert(err);
throw err;
} finally {
trackButtonIcon.classList.remove("trektor-state-loading");
}
});

addButton.addEventListener("click", async () => {
try {
console.log("adding task")
await browser.runtime.sendMessage({
action: "addTask",
action: 'addTask',
args: [window.location.pathname.split("/", 3)[2]],
});
} catch (err) {
window.alert(err);
throw err;
}
});
}
Expand Down Expand Up @@ -82,7 +86,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 @@
BackgroundScript.init(getTrektor())
14 changes: 10 additions & 4 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 = {
trelloGateway: new TrelloGateway(browser.storage.local),
togglGateway: new TogglGateway(browser.storage.local),
};
function getTrektor() {
return {
trelloGateway: new TrelloGateway(browser.storage.local),
togglGateway: new TogglGateway(browser.storage.local),
}
}