feat(terminal): add guided goals, tasks, and confirmed deletion

This commit is contained in:
Codex committed 2026-09-28 14:13:21 +08:00
1 parent 8ffeb2897f
commit 78c1ffe1fd
7 files changed
+175 -10

No files matched your search

+28
View File
@@ -34,6 +34,27 @@
task.done = done; task.completedAt = done ? new Date().toISOString() : null; task.done = done; task.completedAt = done ? new Date().toISOString() : null;
persistBoard(data); return task; persistBoard(data); return task;
}, },
setGoalDone(id, done) {
const data = board(), goal = data.goals.find(item => item.id === id);
if (!goal) throw new Error('notFound');
goal.done = done; goal.completedAt = done ? new Date().toISOString() : null;
persistBoard(data); return goal;
},
deleteTask(id) {
const data = board(), task = data.tasks.find(item => item.id === id);
if (!task) throw new Error('notFound');
data.tasks = data.tasks.filter(item => item.id !== id);
const all = mems();
if (all.some(item => item.taskId === id)) save('lifeos_memories', all.map(item => item.taskId === id ? { ...item, taskId: '', updatedAt: new Date().toISOString() } : item));
persistBoard(data); return task;
},
deleteGoal(id) {
const data = board(), goal = data.goals.find(item => item.id === id);
if (!goal) throw new Error('notFound');
data.goals = data.goals.filter(item => item.id !== id);
data.tasks.forEach(task => { if (task.goalId === id) task.goalId = ''; });
persistBoard(data); return goal;
},
createMemory({ date = day(), title, text = '', mood = 'cloudy', energy = 3, tags = [], location = '', taskId = '', replace = false }) { createMemory({ date = day(), title, text = '', mood = 'cloudy', energy = 3, tags = [], location = '', taskId = '', replace = false }) {
title = String(title || '').trim(); title = String(title || '').trim();
if (title.length > 120) throw new Error('invalidTitle'); if (title.length > 120) throw new Error('invalidTitle');
@@ -45,6 +66,13 @@
const film = filmFor(mood, Number(energy)), now = new Date().toISOString(); const film = filmFor(mood, Number(energy)), now = new Date().toISOString();
const memory = { ...old, id: old?.id || `memory_${crypto.randomUUID()}`, date, title, text: String(text).trim(), mood, energy: Number(energy), tags: [...new Set(tags.map(tag => String(tag).trim()).filter(Boolean))].slice(0, 12), location: String(location).trim().slice(0, 100), taskId, filmTitle: film.filmTitle, filmSubtitle: film.filmSubtitle, filmLine: film.filmLine, createdAt: old?.createdAt || now, updatedAt: now }; const memory = { ...old, id: old?.id || `memory_${crypto.randomUUID()}`, date, title, text: String(text).trim(), mood, energy: Number(energy), tags: [...new Set(tags.map(tag => String(tag).trim()).filter(Boolean))].slice(0, 12), location: String(location).trim().slice(0, 100), taskId, filmTitle: film.filmTitle, filmSubtitle: film.filmSubtitle, filmLine: film.filmLine, createdAt: old?.createdAt || now, updatedAt: now };
save('lifeos_memories', [...all.filter(item => item.date !== date), memory].sort((a, b) => b.date.localeCompare(a.date))); renderAll(); return memory; save('lifeos_memories', [...all.filter(item => item.date !== date), memory].sort((a, b) => b.date.localeCompare(a.date))); renderAll(); return memory;
},
deleteMemory(id) {
const all = mems(), memory = all.find(item => item.id === id);
if (!memory) throw new Error('notFound');
save('lifeos_memories', all.filter(item => item.id !== id));
save('lifeos_museum', museum().filter(item => item.memoryId !== id));
renderAll(); return memory;
} }
}; };
})(); })();
+1 -1
View File
@@ -60,7 +60,7 @@
window.saveMemoryEdit = (event,id) => { event.preventDefault(); const all=mems(), memory=all.find(item=>item.id===id); if(!memory)return; memory.title=document.querySelector('#memory-edit-title').value.trim(); memory.text=document.querySelector('#memory-edit-note').value.trim(); memory.tags=tagList(document.querySelector('#memory-tags').value); memory.location=document.querySelector('#memory-place').value.trim(); memory.taskId=document.querySelector('#memory-task').value||''; memory.updatedAt=new Date().toISOString(); save('lifeos_memories',all); closeModal(); renderAll(); detail(id); }; window.saveMemoryEdit = (event,id) => { event.preventDefault(); const all=mems(), memory=all.find(item=>item.id===id); if(!memory)return; memory.title=document.querySelector('#memory-edit-title').value.trim(); memory.text=document.querySelector('#memory-edit-note').value.trim(); memory.tags=tagList(document.querySelector('#memory-tags').value); memory.location=document.querySelector('#memory-place').value.trim(); memory.taskId=document.querySelector('#memory-task').value||''; memory.updatedAt=new Date().toISOString(); save('lifeos_memories',all); closeModal(); renderAll(); detail(id); };
window.preserve = id => { const all = museum(), alreadyPreserved = all.some(item => item.memoryId === id); if (alreadyPreserved) { save('lifeos_museum', all.filter(item => item.memoryId !== id)); toast(t('timeline.unpreserved')); } else { save('lifeos_museum', [...all, { id: `artifact_${crypto.randomUUID()}`, memoryId: id, preservedAt: new Date().toISOString() }]); toast(t('timeline.added')); } closeModal(); renderAll(); }; window.preserve = id => { const all = museum(), alreadyPreserved = all.some(item => item.memoryId === id); if (alreadyPreserved) { save('lifeos_museum', all.filter(item => item.memoryId !== id)); toast(t('timeline.unpreserved')); } else { save('lifeos_museum', [...all, { id: `artifact_${crypto.randomUUID()}`, memoryId: id, preservedAt: new Date().toISOString() }]); toast(t('timeline.added')); } closeModal(); renderAll(); };
window.confirmDelete = id => { rootModal().innerHTML = `<div class="modal"><div class="modalbox"><div class="eyebrow">${t('timeline.confirmDelete')}</div><h2>${t('timeline.remove')}</h2><p class="sub">${t('timeline.irreversible')}</p><div class="actions"><button class="danger" onclick="deleteMemory('${id}')">${t('timeline.deleteMemory')}</button><button class="secondary" onclick="modal.innerHTML=''">${t('common.cancel')}</button></div></div></div>`; }; window.confirmDelete = id => { rootModal().innerHTML = `<div class="modal"><div class="modalbox"><div class="eyebrow">${t('timeline.confirmDelete')}</div><h2>${t('timeline.remove')}</h2><p class="sub">${t('timeline.irreversible')}</p><div class="actions"><button class="danger" onclick="deleteMemory('${id}')">${t('timeline.deleteMemory')}</button><button class="secondary" onclick="modal.innerHTML=''">${t('common.cancel')}</button></div></div></div>`; };
window.deleteMemory = id => { save('lifeos_memories', mems().filter(item => item.id !== id)); save('lifeos_museum', museum().filter(item => item.memoryId !== id)); rootModal().innerHTML = ''; renderAll(); toast(t('timeline.removed')); }; window.deleteMemory = id => { window.LifeOSActions.deleteMemory(id); rootModal().innerHTML = ''; toast(t('timeline.removed')); };
let insightView = 'week', insightOffset = 0; let insightView = 'week', insightOffset = 0;
const reportDateKey = value => `${value.getFullYear()}-${String(value.getMonth() + 1).padStart(2, '0')}-${String(value.getDate()).padStart(2, '0')}`; const reportDateKey = value => `${value.getFullYear()}-${String(value.getMonth() + 1).padStart(2, '0')}-${String(value.getDate()).padStart(2, '0')}`;
const reportDayNumber = value => Date.UTC(value.getFullYear(), value.getMonth(), value.getDate()) / 864e5; const reportDayNumber = value => Date.UTC(value.getFullYear(), value.getMonth(), value.getDate()) / 864e5;
+18 -1
View File
@@ -564,7 +564,7 @@
"invalidDate": "Use a real date in YYYY-MM-DD format, today, or tomorrow.", "invalidDate": "Use a real date in YYYY-MM-DD format, today, or tomorrow.",
"invalidPriority": "Priority must be high, normal, or low.", "invalidPriority": "Priority must be high, normal, or low.",
"invalidValue": "Weather or energy value is invalid.", "invalidValue": "Weather or energy value is invalid.",
"notFound": "The referenced task or goal was not found.", "notFound": "The referenced record was not found.",
"ambiguous": "More than one item has this title. Use its ID instead.", "ambiguous": "More than one item has this title. Use its ID instead.",
"memoryExists": "A memory already exists for that date. Edit it in Today or Timeline.", "memoryExists": "A memory already exists for that date. Edit it in Today or Timeline.",
"invalidOption": "Unknown option or missing option value. Type help.", "invalidOption": "Unknown option or missing option value. Type help.",
@@ -584,6 +584,23 @@
"location": "Place or scene? [{value}]", "location": "Place or scene? [{value}]",
"taskId": "Related task? [{value}] — enter a list number, title, or ID\n{tasks}", "taskId": "Related task? [{value}] — enter a list number, title, or ID\n{tasks}",
"controls": "Enter keeps the displayed value; back/cancel controls the flow. Use - to clear an optional field." "controls": "Enter keeps the displayed value; back/cancel controls the flow. Use - to clear an optional field."
},
"goalCompleted": "Goal completed: {title}",
"goalReopened": "Goal reopened: {title}",
"confirmAnswer": "Type yes to confirm or no to cancel.",
"flow": {
"title": "Title? (required; current: {value})",
"description": "Description? [{value}] (optional)",
"targetDate": "Target date? [{value}] — YYYY-MM-DD, today, or tomorrow",
"showOnDesktop": "Show countdown on desktop? [{value}] — yes/no",
"dueDate": "Due date? [{value}] — YYYY-MM-DD, today, or tomorrow",
"goalId": "Link to a goal? [{value}] — number, title, or ID\n{items}",
"priority": "Priority? [{value}] — high / normal / low",
"pick": "Choose a {kind} by number, title, date, or ID:",
"confirmDelete": "Delete {kind}: {title}? Type yes to delete or no to cancel.",
"confirmCreate": "Create {kind}: {title}? Type yes to save or no to cancel.",
"cancelled": "Operation cancelled.",
"deleted": "Deleted {kind}: {title}"
} }
} }
} }
Loaded 3 of 7 files, more files were not shown because too many files have changed in this diff. Show more