Skip to content
This repository was archived by the owner on Nov 11, 2025. It is now read-only.

Commit f8f0872

Browse files
Player005ushi-as
authored andcommitted
initial commit
0 parents  commit f8f0872

11 files changed

Lines changed: 407 additions & 0 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
web-ext-artifacts

.web-extension-id

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# This file was created by https://github.com/mozilla/web-ext
2+
# Your auto-generated extension ID for addons.mozilla.org is:
3+
trektor@aboutsource.net

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# trektor 🚜
2+
3+
Browser-Extension zum automatischen Anlegen von Toggl tracking tasks.

background.js

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
trektor.runtime.onMessage.addListener(async (msg) => {
2+
switch (msg.action) {
3+
case 'track':
4+
await track(...msg.args);
5+
return;
6+
case 'addTask':
7+
await addTask(...msg.args);
8+
return;
9+
default:
10+
throw new Error(`unknown action: ${msg.action}`);
11+
}
12+
});
13+
14+
async function track(cardId) {
15+
const task = await addTask(cardId);
16+
const response = await trektor.togglGateway.startTimeEntry(task.id);
17+
return response.data;
18+
}
19+
20+
async function addTask(cardId) {
21+
const card = await trektor.trelloGateway.getCard(cardId);
22+
23+
const taskPrefixes = card.labels
24+
.map((label) => label.name.match(/(?<=#)[a-z0-9]+$/)?.[0])
25+
.filter((prefix) => prefix !== undefined);
26+
27+
if (taskPrefixes.length === 0) {
28+
throw new Error('Card has no valid project labels.');
29+
}
30+
if (taskPrefixes.length > 1) {
31+
throw new Error('Card has multiple project labels.');
32+
}
33+
const taskPrefix = taskPrefixes[0];
34+
const taskName = `${taskPrefix}_${card.idShort}`;
35+
const cardTaskName = card.name.match(/(?<=#)[a-z0-9]+_[0-9]+/)?.[0];
36+
37+
if (cardTaskName === undefined) {
38+
await trektor.trelloGateway.updateCard(card.id, {
39+
name: `${card.name} #${taskName}`,
40+
});
41+
} else if (cardTaskName !== taskName) {
42+
throw new Error('Card name includes invalid tracking task.');
43+
}
44+
const workspaces = await trektor.togglGateway.getWorkspaces();
45+
46+
if (workspaces.length === 0) {
47+
throw new Error('Could not find any toggl workspaces.');
48+
}
49+
if (workspaces.length > 1) {
50+
throw new Error('Found multiple toggl workspaces. Not sure how to deal with that...');
51+
}
52+
const allProjects = await trektor.togglGateway.getProjects(workspaces[0].id);
53+
const projects = allProjects.filter((project) => project.name.endsWith(`(${taskPrefix})`));
54+
55+
if (projects.length === 0) {
56+
throw new Error('Could not find any matching toggl project.');
57+
}
58+
if (projects.length > 1) {
59+
throw new Error('Found multiple matching toggl projects. Not sure how to deal with that...');
60+
}
61+
const tasks = await trektor.togglGateway.getTasks(projects[0].id);
62+
const task = tasks.find((task) => task.name === taskName);
63+
if (task !== undefined) return task;
64+
65+
const response = await trektor.togglGateway.createTask(projects[0].id, taskName)
66+
return response.data;
67+
}

content_script.js

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
async function addButton() {
2+
const sidebar = await awaitSelector(".window-sidebar", 10000);
3+
4+
const module = document.createElement("div");
5+
module.classList.add("window-module", "u-clearfix");
6+
sidebar.prepend(module);
7+
8+
const moduleHeading = document.createElement('h3');
9+
moduleHeading.innerText = 'Trektor';
10+
module.append(moduleHeading);
11+
12+
const buttonList = document.createElement("div");
13+
buttonList.classList.add("u-clearfix");
14+
module.append(buttonList);
15+
16+
const trackButton = document.createElement("span");
17+
trackButton.classList.add("button-link");
18+
buttonList.append(trackButton);
19+
20+
const trackButtonIcon = document.createElement("span");
21+
trackButtonIcon.classList.add("icon-sm", "icon-clock", "trektor-button-icon");
22+
trackButton.append(trackButtonIcon);
23+
24+
const trackButtonText = document.createElement("span");
25+
trackButtonText.innerText = "Trek Now";
26+
trackButton.append(trackButtonText);
27+
28+
const addButton = document.createElement("span");
29+
addButton.classList.add("button-link", "add-button-link");
30+
addButton.innerText = "Toggl Task hinzufügen";
31+
buttonList.append(addButton);
32+
33+
trackButton.addEventListener("click", async () => {
34+
trackButtonIcon.classList.add("trektor-state-loading");
35+
36+
const response = await trektor.runtime.sendMessage({
37+
action: "track",
38+
args: [window.location.pathname.split("/", 3)[2]],
39+
});
40+
trackButtonIcon.classList.remove("trektor-state-loading");
41+
42+
if (response) {
43+
window.alert(response);
44+
} else {
45+
trackButtonIcon.classList.replace("icon-clock", "icon-check-circle");
46+
window.setTimeout(() => trackButtonIcon.classList.replace("icon-check-circle", "icon-clock"), 2000);
47+
}
48+
});
49+
50+
addButton.addEventListener("click", async () => {
51+
const response = await trektor.runtime.sendMessage({
52+
action: "addTask",
53+
args: [window.location.pathname.split("/", 3)[2]],
54+
});
55+
56+
if (response) window.alert(response);
57+
});
58+
}
59+
60+
function awaitSelector(selector, timeout) {
61+
const element = document.querySelector(".window-sidebar");
62+
if (element !== null) return Promise.resolve(element);
63+
64+
return new Promise((resolve, reject) => {
65+
const interval = window.setInterval(() => {
66+
const element = document.querySelector(".window-sidebar");
67+
68+
if (element !== null) {
69+
resolve(element);
70+
window.clearInterval(interval);
71+
} else if (timeout < 0) {
72+
reject(new Error('timeout'));
73+
window.clearInterval(interval);
74+
}
75+
timeout -= 100;
76+
}, 100);
77+
});
78+
}
79+
80+
window.addEventListener("pushstate", () => {
81+
if (window.location.pathname.startsWith("/c/")) addButton();
82+
});
83+
84+
window.addEventListener('load', () => {
85+
if (window.location.pathname.startsWith("/c/")) addButton();
86+
});

content_style.css

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
.trektor-button-icon.trektor-state-loading {
2+
animation-name: trektor-spin;
3+
animation-duration: 1s;
4+
animation-iteration-count: infinite;
5+
animation-timing-function: linear;
6+
}
7+
8+
@keyframes trektor-spin {
9+
from {
10+
transform:rotate(0deg);
11+
}
12+
to {
13+
transform:rotate(360deg);
14+
}
15+
}

icons/64.png

2.65 KB
Loading

manifest.json

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
{
2+
"manifest_version": 2,
3+
"name": "Trektor",
4+
"description": "Browser-Extension zum automatischen Anlegen von Toggl tracking tasks",
5+
"version": "0.0.3",
6+
"browser_specific_settings": {
7+
"gecko": {
8+
"id": "trektor@aboutsource.net"
9+
}
10+
},
11+
"icons": {
12+
"64": "icons/64.png"
13+
},
14+
"content_scripts": [
15+
{
16+
"matches": [
17+
"https://trello.com/*"
18+
],
19+
"js": [
20+
"trektor.js",
21+
"content_script.js"
22+
],
23+
"css": [
24+
"content_style.css"
25+
]
26+
}
27+
],
28+
"background": {
29+
"scripts": [
30+
"trektor.js",
31+
"background.js"
32+
]
33+
},
34+
"permissions": [
35+
"https://api.trello.com/*",
36+
"https://api.track.toggl.com/*",
37+
"storage"
38+
],
39+
"options_ui": {
40+
"page": "options/index.html"
41+
}
42+
}

options/index.html

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<link rel="icon" href="icons/64.png">
5+
<title>Trektor settings</title>
6+
<meta charset="UTF-8">
7+
<style>
8+
a {
9+
display: block;
10+
margin-bottom: 10px;
11+
}
12+
input {
13+
width: 100%;
14+
box-sizing: border-box;
15+
margin-bottom: 10px;
16+
}
17+
</style>
18+
</head>
19+
<body>
20+
<a href="https://trello.com/1/authorize?expiration=90days&scope=read,write&response_type=token&name=Trektor&key=afadffe77f745496f80ebb4bf460c615" target="_blank">
21+
Trello Token
22+
</a>
23+
24+
<input type="text" name="trello" />
25+
26+
<a href="https://track.toggl.com/profile" target="_blank">
27+
Toggl Token
28+
</a>
29+
30+
<input type="text" name="toggl" />
31+
32+
<script src="../trektor.js"></script>
33+
<script src="./script.js"></script>
34+
</body>
35+
</html>

options/script.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
document.querySelectorAll("input").forEach((field) => {
2+
field.addEventListener("input", (e) => {
3+
trektor.storage.set({ [e.target.name]: e.target.value });
4+
});
5+
6+
trektor.storage.get(field.name).then(({ [field.name]: value }) => {
7+
field.value = (value === undefined) ? '' : value;
8+
});
9+
});

0 commit comments

Comments
 (0)