239 lines
7.8 KiB
JavaScript
239 lines
7.8 KiB
JavaScript
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 {};
|