const BADGE_CLASS = 'taskdecay-injected-badge'; function detectTaskManager() { const host = window.location.host; if (host.includes('todoist.com')) return 'todoist'; if (host.includes('ticktick.com')) return 'ticktick'; if (host.includes('notion.so')) return 'notion'; return null; } function extractTaskIdFromUrl() { const url = window.location.href; const platform = detectTaskManager(); if (platform === 'todoist') { const m = url.match(/task\/(\d+)/); if (m) return `todoist-${m[1]}`; } if (platform === 'ticktick') { const m = url.match(/task\/(\w+)/); if (m) return `ticktick-${m[1]}`; } if (platform === 'notion') { const m = url.match(/([a-f0-9]{32})/); if (m) return `notion-${m[1]}`; } return null; } function injectStyles() { if (document.getElementById('taskdecay-styles')) return; const style = document.createElement('style'); style.id = 'taskdecay-styles'; style.textContent = ` .taskdecay-badge { display: inline-flex; align-items: center; gap: 4px; padding: 2px 8px; border-radius: 4px; font-size: 11px; font-weight: 600; font-family: system-ui, -apple-system, sans-serif; margin-left: 8px; cursor: pointer; transition: opacity 0.2s; } .taskdecay-badge:hover { opacity: 0.8; } .taskdecay-badge-warm { background: #f39c12; color: #fff; } .taskdecay-badge-hot { background: #e74c3c; color: #fff; } .taskdecay-badge-cold { background: #3498db; color: #fff; } .taskdecay-toast { position: fixed; bottom: 20px; right: 20px; background: #1a1a1a; color: #fff; padding: 12px 16px; border-radius: 8px; font-family: system-ui, -apple-system, sans-serif; font-size: 13px; z-index: 999999; box-shadow: 0 4px 12px rgba(0,0,0,0.3); max-width: 320px; line-height: 1.4; } .taskdecay-toast a { color: #58a6ff; text-decoration: none; display: block; margin-top: 8px; } .taskdecay-toast button { background: #e74c3c; border: none; color: #fff; padding: 6px 12px; border-radius: 4px; cursor: pointer; margin-top: 8px; font-size: 12px; } `; document.head.appendChild(style); } function showToast(message, action) { const existing = document.querySelector('.taskdecay-toast'); if (existing) existing.remove(); const toast = document.createElement('div'); toast.className = 'taskdecay-toast'; toast.textContent = message; if (action) { const btn = document.createElement('button'); btn.textContent = action.label; btn.onclick = () => { action.onClick(); toast.remove(); }; toast.appendChild(btn); } document.body.appendChild(toast); setTimeout(() => toast.remove(), 8000); } async function getTaskDecayInfo(taskId) { return new Promise((resolve) => { chrome.runtime.sendMessage({ type: 'getReviewTasks' }, (response) => { if (!response || response.error) { resolve(null); return; } const match = response.review?.find((r) => r.task.id === taskId); if (match) { resolve({ taskId: match.task.id, score: match.score.score, ageDays: match.score.ageDays, explanation: match.score.explanation, }); } else { resolve(null); } }); }); } function addBadgeToTodoist(info) { const selectors = [ '[data-testid="task-details-modal"] [data-testid="task-title"]', '[data-testid="task-details-modal"] h1', '.task_content', '[data-item-id] .task_content', ]; for (const sel of selectors) { const el = document.querySelector(sel); if (el && !el.parentElement?.querySelector('.taskdecay-badge')) { const badge = document.createElement('span'); badge.className = `taskdecay-badge ${info.score >= 70 ? 'taskdecay-badge-hot' : info.score >= 40 ? 'taskdecay-badge-warm' : 'taskdecay-badge-cold'}`; badge.textContent = `${info.score} decay${info.ageDays > 1 ? ` · ${Math.round(info.ageDays)}d` : ''}`; badge.title = info.explanation; badge.onclick = (e) => { e.stopPropagation(); chrome.runtime.sendMessage({ type: 'openReview' }); }; el.parentElement?.insertBefore(badge, el.nextSibling); break; } } } function addBadgeToTickTick(info) { const selectors = [ '.task-title', '.task-title-text', '[class*="taskTitle"]', 'h1', ]; for (const sel of selectors) { const el = document.querySelector(sel); if (el && !el.parentElement?.querySelector('.taskdecay-badge')) { const badge = document.createElement('span'); badge.className = `taskdecay-badge ${info.score >= 70 ? 'taskdecay-badge-hot' : info.score >= 40 ? 'taskdecay-badge-warm' : 'taskdecay-badge-cold'}`; badge.textContent = `${info.score} decay${info.ageDays > 1 ? ` · ${Math.round(info.ageDays)}d` : ''}`; badge.title = info.explanation; badge.onclick = (e) => { e.stopPropagation(); chrome.runtime.sendMessage({ type: 'openReview' }); }; el.parentElement?.insertBefore(badge, el.nextSibling); break; } } } function addBadgeToNotion(info) { const selectors = [ '[data-testid="page-title"]', '.notion-page-block', 'h1', '[class*="title" i]', ]; for (const sel of selectors) { const el = document.querySelector(sel); if (el && !el.parentElement?.querySelector('.taskdecay-badge')) { const badge = document.createElement('span'); badge.className = `taskdecay-badge ${info.score >= 70 ? 'taskdecay-badge-hot' : info.score >= 40 ? 'taskdecay-badge-warm' : 'taskdecay-badge-cold'}`; badge.textContent = `${info.score} decay${info.ageDays > 1 ? ` · ${Math.round(info.ageDays)}d` : ''}`; badge.title = info.explanation; badge.onclick = (e) => { e.stopPropagation(); chrome.runtime.sendMessage({ type: 'openReview' }); }; el.parentElement?.insertBefore(badge, el.nextSibling); break; } } } async function injectBadge() { const taskId = extractTaskIdFromUrl(); if (!taskId) return; const info = await getTaskDecayInfo(taskId); if (!info) return; const platform = detectTaskManager(); if (platform === 'todoist') addBadgeToTodoist(info); else if (platform === 'ticktick') addBadgeToTickTick(info); else if (platform === 'notion') addBadgeToNotion(info); } function init() { injectStyles(); injectBadge(); // Re-inject on URL changes (SPA navigation) let lastUrl = window.location.href; const observer = new MutationObserver(() => { if (window.location.href !== lastUrl) { lastUrl = window.location.href; setTimeout(injectBadge, 800); } }); observer.observe(document.body, { childList: true, subtree: true }); } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init); } else { init(); } chrome.runtime.onMessage.addListener((message) => { const msg = message; if (msg.type === 'showToast') { showToast(msg.text, msg.action ? { label: msg.action.label, onClick: () => { chrome.runtime.sendMessage({ type: msg.action.type }); } } : undefined); } }); export {};