diff --git a/CHANGELOG.md b/CHANGELOG.md index 4c44286..1889787 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,12 @@ 本项目按 `v主版本.次版本.修订版本` 标记应用版本;按需创建 Git tag。下列内容以对应提交和已发布功能为准。 +## v1.18.1 — 2026-10-02 + +- 目标与待办新增编辑入口:可修改待办标题、截止日期、优先级及关联目标,也可修改目标标题、备注、目标日期和桌面倒计时选项。 +- 已完成的目标与待办同样支持编辑;保留记录 ID、创建时间、完成状态及现有关联,沿用记录级云同步。 +- 修正验证脚本对固定“今日”日期的假设,并覆盖编辑表单和数据写入。 + ## v1.18.0 — 2026-09-28 - 日历按语言地区和浏览器区域推断国家或地区,并提供独立选择;选择仅保存在当前设备,可随时切回自动模式。 diff --git a/README.md b/README.md index 3c31525..9a77b14 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ 以桌面操作系统为交互方式的个人记录网站。用 App 记录日常、管理目标与待办,并沿时间线回看已经留下的内容。 -在线站点:[Life OS](https://life-os-personal-desktop.biss0.chatgpt.site) · 当前应用版本:**v1.18.0** · [更新日志](CHANGELOG.md) +在线站点:[Life OS](https://life-os-personal-desktop.biss0.chatgpt.site) · 当前应用版本:**v1.18.1** · [更新日志](CHANGELOG.md) ## 功能 @@ -11,7 +11,7 @@ | 今日 | 为选定日期写记忆,记录天气、1–5 能量、标题、标签、地点和关联任务;每日电影是写作提示,不会替代记忆标题。 | | 时间线 | 按日期浏览记忆,使用关键词或标签筛选,编辑、删除和收藏。 | | 博物馆 | 回看收藏的记忆。 | -| 目标与待办 | 创建目标和任务,设置截止日期、优先级和完成状态;目标倒计时可选择显示在桌面。 | +| 目标与待办 | 创建和编辑目标、待办,调整日期、优先级、关联目标与完成状态;目标倒计时可选择显示在桌面。 | | 日历 | 在月视图与日视图中查看记忆、任务、目标、节日和公共假期;按界面语言地区自动选择日历国家,也可手动切换,为选定日期新增记忆。 | | 数据库管理器 | 在再次登录并确认风险后,按表查看、创建、编辑和删除当前账号的云端记录。 | | 洞察 | 周报和月报展示记录频率、完成任务数、常见天气、能量记录及收藏或标记高光的记忆;仅依据已保存的数据统计。 | diff --git a/package.json b/package.json index 5e4e727..d57b354 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "life-os", - "version": "1.18.0", + "version": "1.18.1", "private": true, "type": "module", "scripts": { diff --git a/public/addons.js b/public/addons.js index f18ac36..a107ca8 100644 --- a/public/addons.js +++ b/public/addons.js @@ -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 => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[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); diff --git a/public/data-actions.js b/public/data-actions.js index 121713c..c36e6bd 100644 --- a/public/data-actions.js +++ b/public/data-actions.js @@ -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'); diff --git a/public/i18n-renderers.js b/public/i18n-renderers.js index 2819643..1db99c7 100644 --- a/public/i18n-renderers.js +++ b/public/i18n-renderers.js @@ -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 = `
${detected}
${t('system.local')}
${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))})}
${t('system.stored')}
${t('system.backups')}
${t('system.lastBackup',{value:state.lastBackup ? short(state.lastBackup.slice(0,10)) : t('common.never')})}${oldBackup ? `
${t('system.recommended')}` : ''}
${t('system.release',{version:window.LifeOSVersion||'1.18.0'})}
${detected}
${t('system.local')}
${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))})}
${t('system.stored')}
${t('system.backups')}
${t('system.lastBackup',{value:state.lastBackup ? short(state.lastBackup.slice(0,10)) : t('common.never')})}${oldBackup ? `
${t('system.recommended')}` : ''}
${t('system.release',{version:window.LifeOSVersion||'1.18.1'})}
${t('dialog.exported',{date:short(data.exportedAt.slice(0,10))})}
${t('dialog.memories',{count:data.memories.length})}
${t('dialog.museumItems',{count:data.museum.length})}
${t('dialog.goals',{count:board.goals?.length || 0})}
${t('dialog.tasks',{count:board.tasks?.length || 0})}
${t('dialog.replaceWarning')}
${LifeI18n.t('shell.todaySummary',{memories:mems().length,preserved:museum().length})}
`}function hideContext(){document.querySelector('#life-context')?.remove()}function showContext(e,id){e.preventDefault();e.stopPropagation();hideContext();let labels={open:LifeI18n.t('shell.open'),min:LifeI18n.t('shell.minimize'),close:LifeI18n.t('shell.menuCloseWindow'),refresh:LifeI18n.t('shell.refresh'),new:LifeI18n.t('shell.newToday'),system:LifeI18n.t('shell.systemSettings')},actions=id?`${windows[id]?``:''}`:`${LifeI18n.t('shell.todaySummary',{memories:mems().length,preserved:museum().length})}
`}function hideContext(){document.querySelector('#life-context')?.remove()}function showContext(e,id){e.preventDefault();e.stopPropagation();hideContext();let labels={open:LifeI18n.t('shell.open'),min:LifeI18n.t('shell.minimize'),close:LifeI18n.t('shell.menuCloseWindow'),refresh:LifeI18n.t('shell.refresh'),new:LifeI18n.t('shell.newToday'),system:LifeI18n.t('shell.systemSettings')},actions=id?`${windows[id]?``:''}`:`How was your day?
${i18n('How was your day?')}
Local Storage
${mems().length} memories · ${museum().length} museum items · Approx. ${Math.ceil(bytes/1024)} KB
Your data is stored locally in this browser.
Create regular backups to prevent data loss.
${s.lastBackup?'LAST BACKUP · '+fmtDate(s.lastBackup.slice(0,10)):'LAST BACKUP · NEVER'} ${rec?'
BACKUP RECOMMENDED':''}
Life OS
Version ${window.LifeOSVersion||'1.18.0'} · Schema v2
Exported: ${fmtDate(d.exportedAt.slice(0,10))}
Memories: ${d.memories.length}
Museum Items: ${d.museum.length}
This will replace all current Life OS data.
Export a backup first if you want to keep it.
Local Storage
${mems().length} memories · ${museum().length} museum items · Approx. ${Math.ceil(bytes/1024)} KB
Your data is stored locally in this browser.
Create regular backups to prevent data loss.
${s.lastBackup?'LAST BACKUP · '+fmtDate(s.lastBackup.slice(0,10)):'LAST BACKUP · NEVER'} ${rec?'
BACKUP RECOMMENDED':''}
Life OS
Version ${window.LifeOSVersion||'1.18.1'} · Schema v2
Exported: ${fmtDate(d.exportedAt.slice(0,10))}
Memories: ${d.memories.length}
Museum Items: ${d.museum.length}
This will replace all current Life OS data.
Export a backup first if you want to keep it.