fix(tasks): enable editing goals and tasks

This commit is contained in:
Codex committed 2026-10-02 10:48:13 +00:00
1 parent 139e4c5198
commit 0a68782172
23 files changed
+191 -34

No files matched your search

+1 -1
View File
@@ -3,7 +3,7 @@
const DRAFT_KEY = 'lifeos_today_draft_v1';
const localDay = () => { const d = new Date(); return `${d.getFullYear()}-${String(d.getMonth()+1).padStart(2,'0')}-${String(d.getDate()).padStart(2,'0')}`; };
const escapeHtml = value => String(value || '').replace(/[&<>]/g, char => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' }[char]));
const APP_VERSION = '1.18.0';
const APP_VERSION = '1.18.1';
window.LifeOSVersion = APP_VERSION;
const appIds = ['today', 'timeline', 'museum', 'insights', 'profile', 'tasks', 'calendar', 'system', 'browser', 'terminal', 'database'];
const t = (key, values) => LifeI18n.t(key, values);
+17
View File
@@ -19,6 +19,14 @@
const goal = { id: `goal_${crypto.randomUUID()}`, title, description: String(description).trim().slice(0, 240), targetDate, showOnDesktop: Boolean(targetDate && showOnDesktop), done: false, createdAt: new Date().toISOString() };
const data = board(); data.goals.unshift(goal); persistBoard(data); return goal;
},
updateGoal(id, { title, description = '', targetDate = '', showOnDesktop = false }) {
title = required(title, 100);
if (!validDate(targetDate)) throw new Error('invalidDate');
const data = board(), goal = data.goals.find(item => item.id === id);
if (!goal) throw new Error('notFound');
Object.assign(goal, { title, description: String(description).trim().slice(0, 240), targetDate, showOnDesktop: Boolean(targetDate && showOnDesktop), updatedAt: new Date().toISOString() });
persistBoard(data); return goal;
},
createTask({ title, dueDate = '', goalId = '', priority = 'normal' }) {
title = required(title, 140);
if (!validDate(dueDate)) throw new Error('invalidDate');
@@ -28,6 +36,15 @@
const task = { id: `task_${crypto.randomUUID()}`, title, dueDate, goalId, priority, done: false, createdAt: new Date().toISOString() };
data.tasks.push(task); persistBoard(data); return task;
},
updateTask(id, { title, dueDate = '', goalId = '', priority = 'normal' }) {
title = required(title, 140);
if (!validDate(dueDate)) throw new Error('invalidDate');
if (!['normal', 'high', 'low'].includes(priority)) throw new Error('invalidPriority');
const data = board(), task = data.tasks.find(item => item.id === id);
if (!task || (goalId && !data.goals.some(goal => goal.id === goalId))) throw new Error('notFound');
Object.assign(task, { title, dueDate, goalId, priority, updatedAt: new Date().toISOString() });
persistBoard(data); return task;
},
setTaskDone(id, done) {
const data = board(), task = data.tasks.find(item => item.id === id);
if (!task) throw new Error('notFound');
+1 -1
View File
@@ -110,7 +110,7 @@
window.saveProfile = () => { save('lifeos_profile', { name: pname.value, birthday: pbday.value, startDate: pstart.value || day(), motto: pmotto.value }); renderAll(); toast(t('profile.saved')); };
window.systemApp = container => {
const state = settings(), preference = LifeI18n.preference(), bytes = new Blob([JSON.stringify({ mems: mems(), museum: museum(), profile: profile(), system: sys() })]).size, oldBackup = state.lastBackup && Date.now() - new Date(state.lastBackup) > 12096e5, detected = t('system.browserDetected', { name: LifeI18n.names[LifeI18n.browserLanguage()] });
container.innerHTML = `<div class="eyebrow">${t('system.eyebrow')}</div><h1>${t('system.title')}</h1><div class="settings"><section data-settings-group="appearance"><div class="eyebrow">${t('system.appearance')}</div><div class="choices">${['dark','light','auto'].map(item => `<button class="chip ${state.theme === item ? 'selected' : ''}" onclick="theme('${item}')">${t(`system.theme.${item}`)}</button>`).join('')}</div></section><section data-settings-group="language"><div class="eyebrow">${t('system.language')}</div><p class="sub">${detected}</p><div class="choices language-choices"><button class="chip ${preference === 'auto' ? 'selected' : ''}" onclick="window.setLanguage('auto')">${t('system.auto')}</button>${LifeI18n.supported.map(code => `<button class="chip ${preference === code ? 'selected' : ''}" lang="${code}" dir="${code === 'ar' ? 'rtl' : 'ltr'}" onclick="window.setLanguage('${code}')">${LifeI18n.names[code]}</button>`).join('')}</div></section><section class="notification-settings" data-settings-group="notifications">${window.LifeOSNotifications?.markup() || ''}</section><section data-settings-group="data"><div class="eyebrow">${t('system.data')}</div><div class="actions"><button class="primary" onclick="exportData()">${t('system.export')}</button><button class="secondary" onclick="document.querySelector('#importer').click()">${t('system.import')}</button><button class="danger" onclick="resetConfirm()">${t('system.reset')}</button><input id="importer" class="hidden" type="file" accept=".json" onchange="readImport(this)"></div></section><section data-settings-group="data"><div class="eyebrow">${t('system.storage')}</div><p>${t('system.local')}<br>${t('system.storageSummary',{memories:LifeI18n.number(mems().length),museum:LifeI18n.number(museum().length),tasks:LifeI18n.number(sys().tasksBoard?.tasks?.length||0),size:LifeI18n.number(Math.ceil(bytes/1024))})}</p></section><section data-settings-group="data"><div class="eyebrow">${t('system.dataStorage')}</div><p>${t('system.stored')}<br>${t('system.backups')}</p><p>${t('system.lastBackup',{value:state.lastBackup ? short(state.lastBackup.slice(0,10)) : t('common.never')})}${oldBackup ? `<br><b style="color:var(--accent)">${t('system.recommended')}</b>` : ''}</p></section><section data-settings-group="about"><div class="eyebrow">${t('system.version')}</div><p>${t('system.release',{version:window.LifeOSVersion||'1.18.0'})}</p></section></div>`;
container.innerHTML = `<div class="eyebrow">${t('system.eyebrow')}</div><h1>${t('system.title')}</h1><div class="settings"><section data-settings-group="appearance"><div class="eyebrow">${t('system.appearance')}</div><div class="choices">${['dark','light','auto'].map(item => `<button class="chip ${state.theme === item ? 'selected' : ''}" onclick="theme('${item}')">${t(`system.theme.${item}`)}</button>`).join('')}</div></section><section data-settings-group="language"><div class="eyebrow">${t('system.language')}</div><p class="sub">${detected}</p><div class="choices language-choices"><button class="chip ${preference === 'auto' ? 'selected' : ''}" onclick="window.setLanguage('auto')">${t('system.auto')}</button>${LifeI18n.supported.map(code => `<button class="chip ${preference === code ? 'selected' : ''}" lang="${code}" dir="${code === 'ar' ? 'rtl' : 'ltr'}" onclick="window.setLanguage('${code}')">${LifeI18n.names[code]}</button>`).join('')}</div></section><section class="notification-settings" data-settings-group="notifications">${window.LifeOSNotifications?.markup() || ''}</section><section data-settings-group="data"><div class="eyebrow">${t('system.data')}</div><div class="actions"><button class="primary" onclick="exportData()">${t('system.export')}</button><button class="secondary" onclick="document.querySelector('#importer').click()">${t('system.import')}</button><button class="danger" onclick="resetConfirm()">${t('system.reset')}</button><input id="importer" class="hidden" type="file" accept=".json" onchange="readImport(this)"></div></section><section data-settings-group="data"><div class="eyebrow">${t('system.storage')}</div><p>${t('system.local')}<br>${t('system.storageSummary',{memories:LifeI18n.number(mems().length),museum:LifeI18n.number(museum().length),tasks:LifeI18n.number(sys().tasksBoard?.tasks?.length||0),size:LifeI18n.number(Math.ceil(bytes/1024))})}</p></section><section data-settings-group="data"><div class="eyebrow">${t('system.dataStorage')}</div><p>${t('system.stored')}<br>${t('system.backups')}</p><p>${t('system.lastBackup',{value:state.lastBackup ? short(state.lastBackup.slice(0,10)) : t('common.never')})}${oldBackup ? `<br><b style="color:var(--accent)">${t('system.recommended')}</b>` : ''}</p></section><section data-settings-group="about"><div class="eyebrow">${t('system.version')}</div><p>${t('system.release',{version:window.LifeOSVersion||'1.18.1'})}</p></section></div>`;
};
window.importPreview = () => { const data = importData, board = data.system?.tasksBoard || {}; rootModal().innerHTML = `<div class="modal"><div class="modalbox"><div class="eyebrow">${t('dialog.backup')}</div><h2>${t('dialog.ready')}</h2><p class="sub">${t('dialog.exported',{date:short(data.exportedAt.slice(0,10))})}<br>${t('dialog.memories',{count:data.memories.length})}<br>${t('dialog.museumItems',{count:data.museum.length})}<br>${t('dialog.goals',{count:board.goals?.length || 0})}<br>${t('dialog.tasks',{count:board.tasks?.length || 0})}</p><div class="actions"><button class="primary" onclick="applyImport('merge')">${t('dialog.merge')}</button><button class="danger" onclick="replaceConfirm()">${t('dialog.replaceAll')}</button><button class="secondary" onclick="modal.innerHTML=''">${t('common.cancel')}</button></div></div></div>`; };
window.replaceConfirm = () => { rootModal().innerHTML = `<div class="modal"><div class="modalbox"><div class="eyebrow">${t('dialog.final')}</div><h2>${t('dialog.replaceQuestion')}</h2><p class="sub">${t('dialog.replaceWarning')}</p><div class="actions"><button class="danger" onclick="applyImport('replace')">${t('dialog.replace')}</button><button class="secondary" onclick="importPreview()">${t('common.cancel')}</button></div></div></div>`; };
Loaded 3 of 23 files, more files were not shown because too many files have changed in this diff. Show more