TaskDecay v1.0.0 MVP — smart task review & archive Chrome extension
This commit is contained in:
@@ -0,0 +1,146 @@
|
||||
import { fetchAllTasks, getTokenForSource, completeTask, deleteTask } from './apis/index.js';
|
||||
import { getSettings, setSettings, getStoredTasks, setStoredTasks, setLastSync, addDismissedTask, addArchivedTask } from './storage.js';
|
||||
import { getReviewTasks, getAutoArchiveCandidates } from './scoring.js';
|
||||
|
||||
// Background service worker for TaskDecay
|
||||
// Handles periodic sync, badge count, and alarm-based review reminders
|
||||
|
||||
chrome.runtime.onInstalled.addListener(() => {
|
||||
chrome.alarms.create('taskdecay-sync', { periodInMinutes: 60 });
|
||||
chrome.alarms.create('taskdecay-review-reminder', { periodInMinutes: 60 * 24 });
|
||||
updateBadge();
|
||||
});
|
||||
|
||||
chrome.alarms.onAlarm.addListener(async (alarm) => {
|
||||
if (alarm.name === 'taskdecay-sync') {
|
||||
await syncTasks();
|
||||
} else if (alarm.name === 'taskdecay-review-reminder') {
|
||||
await checkReviewReminder();
|
||||
}
|
||||
});
|
||||
|
||||
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
|
||||
const msg = message as any;
|
||||
if (msg.type === 'sync') {
|
||||
syncTasks().then(sendResponse).catch(e => sendResponse({ error: (e as Error).message }));
|
||||
return true;
|
||||
}
|
||||
if (msg.type === 'getReviewTasks') {
|
||||
getReviewData().then(sendResponse).catch(e => sendResponse({ error: (e as Error).message }));
|
||||
return true;
|
||||
}
|
||||
if (msg.type === 'dismiss') {
|
||||
addDismissedTask(msg.taskId).then(() => sendResponse({ ok: true })).catch(e => sendResponse({ error: (e as Error).message }));
|
||||
return true;
|
||||
}
|
||||
if (msg.type === 'archive') {
|
||||
archiveTask(msg.taskId).then(() => sendResponse({ ok: true })).catch(e => sendResponse({ error: (e as Error).message }));
|
||||
return true;
|
||||
}
|
||||
if (msg.type === 'complete') {
|
||||
completeTaskById(msg.taskId).then(() => sendResponse({ ok: true })).catch(e => sendResponse({ error: (e as Error).message }));
|
||||
return true;
|
||||
}
|
||||
if (msg.type === 'delete') {
|
||||
deleteTaskById(msg.taskId).then(() => sendResponse({ ok: true })).catch(e => sendResponse({ error: (e as Error).message }));
|
||||
return true;
|
||||
}
|
||||
if (msg.type === 'openReview') {
|
||||
chrome.tabs.create({ url: chrome.runtime.getURL('src/review.html') });
|
||||
sendResponse({ ok: true });
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
async function syncTasks() {
|
||||
const settings = await getSettings();
|
||||
const tasks = await fetchAllTasks(settings.providers);
|
||||
await setStoredTasks(tasks);
|
||||
await setLastSync(new Date().toISOString());
|
||||
await updateBadge();
|
||||
return { count: tasks.length, syncedAt: new Date().toISOString() };
|
||||
}
|
||||
|
||||
async function getReviewData() {
|
||||
const settings = await getSettings();
|
||||
const tasks = await getStoredTasks();
|
||||
const review = getReviewTasks(tasks, settings);
|
||||
const autoArchive = getAutoArchiveCandidates(tasks, settings);
|
||||
return { review, autoArchiveCount: autoArchive.length, total: tasks.length };
|
||||
}
|
||||
|
||||
async function archiveTask(taskId: string) {
|
||||
const settings = await getSettings();
|
||||
const tasks = await getStoredTasks();
|
||||
const task = tasks.find(t => t.id === taskId);
|
||||
if (!task) throw new Error('Task not found');
|
||||
const token = getTokenForSource(task.source, settings.providers);
|
||||
if (!token) throw new Error('No token for source');
|
||||
await completeTask(task, token);
|
||||
await addArchivedTask(taskId);
|
||||
await updateBadge();
|
||||
}
|
||||
|
||||
async function completeTaskById(taskId: string) {
|
||||
const settings = await getSettings();
|
||||
const tasks = await getStoredTasks();
|
||||
const task = tasks.find(t => t.id === taskId);
|
||||
if (!task) throw new Error('Task not found');
|
||||
const token = getTokenForSource(task.source, settings.providers);
|
||||
if (!token) throw new Error('No token for source');
|
||||
await completeTask(task, token);
|
||||
await addArchivedTask(taskId);
|
||||
await updateBadge();
|
||||
}
|
||||
|
||||
async function deleteTaskById(taskId: string) {
|
||||
const settings = await getSettings();
|
||||
const tasks = await getStoredTasks();
|
||||
const task = tasks.find(t => t.id === taskId);
|
||||
if (!task) throw new Error('Task not found');
|
||||
const token = getTokenForSource(task.source, settings.providers);
|
||||
if (!token) throw new Error('No token for source');
|
||||
await deleteTask(task, token);
|
||||
await addArchivedTask(taskId);
|
||||
await updateBadge();
|
||||
}
|
||||
|
||||
async function checkReviewReminder() {
|
||||
const settings = await getSettings();
|
||||
if (!settings.lastReviewDate) return;
|
||||
const last = new Date(settings.lastReviewDate);
|
||||
const now = new Date();
|
||||
const daysSince = (now.getTime() - last.getTime()) / (1000 * 60 * 60 * 24);
|
||||
if (daysSince >= settings.reviewIntervalDays) {
|
||||
const tasks = await getStoredTasks();
|
||||
const review = getReviewTasks(tasks, settings);
|
||||
if (review.length > 0) {
|
||||
chrome.notifications.create('review-reminder', {
|
||||
type: 'basic',
|
||||
iconUrl: 'icon128.png',
|
||||
title: 'TaskDecay Review Ready',
|
||||
message: `${review.length} tasks need your attention. Open the review session to clean your backlog.`,
|
||||
buttons: [{ title: 'Start Review' }],
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function updateBadge() {
|
||||
try {
|
||||
const settings = await getSettings();
|
||||
const tasks = await getStoredTasks();
|
||||
const review = getReviewTasks(tasks, settings);
|
||||
const count = review.length;
|
||||
chrome.action.setBadgeText({ text: count > 0 ? String(count) : '' });
|
||||
chrome.action.setBadgeBackgroundColor({ color: '#e74c3c' });
|
||||
} catch {
|
||||
chrome.action.setBadgeText({ text: '' });
|
||||
}
|
||||
}
|
||||
|
||||
chrome.notifications.onButtonClicked.addListener((notificationId) => {
|
||||
if (notificationId === 'review-reminder') {
|
||||
chrome.tabs.create({ url: chrome.runtime.getURL('src/review.html') });
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user