chore: initial commit of TaskTracker project
Existing ASP.NET API with vanilla JS SPA, WindowWatcher, Chrome extension, and MCP server. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
55
ChromeExtension/background.js
Normal file
55
ChromeExtension/background.js
Normal file
@@ -0,0 +1,55 @@
|
||||
const API_BASE = "http://localhost:5200";
|
||||
let debounceTimer = null;
|
||||
let lastReportedUrl = "";
|
||||
let lastReportedTitle = "";
|
||||
|
||||
function reportTab(tab) {
|
||||
if (!tab || !tab.url || tab.url.startsWith("chrome://") || tab.url.startsWith("chrome-extension://")) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Skip if same as last reported
|
||||
if (tab.url === lastReportedUrl && tab.title === lastReportedTitle) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Debounce: wait 2 seconds before reporting
|
||||
if (debounceTimer) {
|
||||
clearTimeout(debounceTimer);
|
||||
}
|
||||
|
||||
debounceTimer = setTimeout(() => {
|
||||
const payload = {
|
||||
source: "ChromeExtension",
|
||||
appName: "chrome.exe",
|
||||
windowTitle: tab.title || "",
|
||||
url: tab.url
|
||||
};
|
||||
|
||||
fetch(`${API_BASE}/api/context`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(payload)
|
||||
})
|
||||
.then(() => {
|
||||
lastReportedUrl = tab.url;
|
||||
lastReportedTitle = tab.title;
|
||||
})
|
||||
.catch(err => console.warn("Failed to report context:", err));
|
||||
}, 2000);
|
||||
}
|
||||
|
||||
// Tab switched
|
||||
chrome.tabs.onActivated.addListener((activeInfo) => {
|
||||
chrome.tabs.get(activeInfo.tabId, (tab) => {
|
||||
if (chrome.runtime.lastError) return;
|
||||
reportTab(tab);
|
||||
});
|
||||
});
|
||||
|
||||
// Navigation within tab
|
||||
chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
|
||||
if (changeInfo.status === "complete" && tab.active) {
|
||||
reportTab(tab);
|
||||
}
|
||||
});
|
||||
16
ChromeExtension/manifest.json
Normal file
16
ChromeExtension/manifest.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"manifest_version": 3,
|
||||
"name": "Work Context Tracker",
|
||||
"version": "1.0",
|
||||
"description": "Reports active tab URL and title to the Work Context Tracker API",
|
||||
"permissions": ["tabs", "activeTab", "storage"],
|
||||
"background": {
|
||||
"service_worker": "background.js"
|
||||
},
|
||||
"action": {
|
||||
"default_popup": "popup.html"
|
||||
},
|
||||
"host_permissions": [
|
||||
"http://localhost:5200/*"
|
||||
]
|
||||
}
|
||||
60
ChromeExtension/popup.html
Normal file
60
ChromeExtension/popup.html
Normal file
@@ -0,0 +1,60 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<style>
|
||||
body {
|
||||
width: 300px;
|
||||
padding: 12px;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
||||
font-size: 13px;
|
||||
color: #333;
|
||||
}
|
||||
h3 {
|
||||
margin: 0 0 8px 0;
|
||||
font-size: 14px;
|
||||
color: #1a73e8;
|
||||
}
|
||||
.task-title {
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
.task-status {
|
||||
display: inline-block;
|
||||
padding: 2px 8px;
|
||||
border-radius: 12px;
|
||||
font-size: 11px;
|
||||
font-weight: 500;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.status-Active { background: #e8f5e9; color: #2e7d32; }
|
||||
.status-Paused { background: #fff3e0; color: #e65100; }
|
||||
.status-Pending { background: #e3f2fd; color: #1565c0; }
|
||||
.no-task {
|
||||
color: #888;
|
||||
font-style: italic;
|
||||
}
|
||||
button {
|
||||
display: block;
|
||||
width: 100%;
|
||||
padding: 8px;
|
||||
margin-top: 8px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 6px;
|
||||
background: #fff;
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
}
|
||||
button:hover { background: #f5f5f5; }
|
||||
button:disabled { opacity: 0.5; cursor: not-allowed; }
|
||||
#error { color: #d32f2f; font-size: 11px; margin-top: 8px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h3>Work Context Tracker</h3>
|
||||
<div id="content">Loading...</div>
|
||||
<div id="error"></div>
|
||||
<script src="popup.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
62
ChromeExtension/popup.js
Normal file
62
ChromeExtension/popup.js
Normal file
@@ -0,0 +1,62 @@
|
||||
const API_BASE = "http://localhost:5200";
|
||||
const content = document.getElementById("content");
|
||||
const errorDiv = document.getElementById("error");
|
||||
|
||||
let currentTask = null;
|
||||
|
||||
async function loadActiveTask() {
|
||||
try {
|
||||
const res = await fetch(`${API_BASE}/api/tasks/active`);
|
||||
const json = await res.json();
|
||||
|
||||
if (!json.success || !json.data) {
|
||||
content.innerHTML = '<p class="no-task">No active task</p>';
|
||||
return;
|
||||
}
|
||||
|
||||
currentTask = json.data;
|
||||
renderTask(currentTask);
|
||||
} catch (err) {
|
||||
errorDiv.textContent = "Cannot connect to API";
|
||||
}
|
||||
}
|
||||
|
||||
function renderTask(task) {
|
||||
const isActive = task.status === "Active";
|
||||
const isPaused = task.status === "Paused";
|
||||
|
||||
content.innerHTML = `
|
||||
<div class="task-title">${escapeHtml(task.title)}</div>
|
||||
<span class="task-status status-${task.status}">${task.status}</span>
|
||||
${task.category ? `<div style="font-size:11px;color:#666;">Category: ${escapeHtml(task.category)}</div>` : ""}
|
||||
<button id="toggleBtn">${isActive ? "Pause Task" : "Resume Task"}</button>
|
||||
`;
|
||||
|
||||
document.getElementById("toggleBtn").addEventListener("click", () => toggleTask(task.id, isActive));
|
||||
}
|
||||
|
||||
async function toggleTask(taskId, isCurrentlyActive) {
|
||||
try {
|
||||
const action = isCurrentlyActive ? "pause" : "resume";
|
||||
const res = await fetch(`${API_BASE}/api/tasks/${taskId}/${action}`, {
|
||||
method: "PUT",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({})
|
||||
});
|
||||
const json = await res.json();
|
||||
if (json.success && json.data) {
|
||||
currentTask = json.data;
|
||||
renderTask(currentTask);
|
||||
}
|
||||
} catch (err) {
|
||||
errorDiv.textContent = "Failed to update task";
|
||||
}
|
||||
}
|
||||
|
||||
function escapeHtml(text) {
|
||||
const div = document.createElement("div");
|
||||
div.textContent = text;
|
||||
return div.innerHTML;
|
||||
}
|
||||
|
||||
loadActiveTask();
|
||||
Reference in New Issue
Block a user