feat(tasks): add goals and task management app

This commit is contained in:
Codex committed 2026-09-27 12:47:39 +00:00
1 parent 352c3f737c
commit 1e7f8ed549
10 files changed
+124 -23

No files matched your search

+5 -2
View File
@@ -10,11 +10,13 @@
- **Today**:每日文字记录、情绪天气、1–5 能量等级,以及随机生成的“今日电影”。 - **Today**:每日文字记录、情绪天气、1–5 能量等级,以及随机生成的“今日电影”。
- **Timeline**:按时间轴浏览、编辑和删除记忆。 - **Timeline**:按时间轴浏览、编辑和删除记忆。
- **Museum**:将重要记忆长期珍藏为馆藏。 - **Museum**:将重要记忆长期珍藏为馆藏。
- **Tasks / Goals**:创建目标和待办,关联任务、设置截止日期与优先级,查看今天/即将开始/已完成事项并追踪目标进度。
- **Insights / Profile**:查看情绪、能量、连续记录与个人系统状态。 - **Insights / Profile**:查看情绪、能量、连续记录与个人系统状态。
- **System**:深浅主题、自动识别浏览器语言(也可固定中文/English)、JSON 导入导出与本地存储说明。 - **System**:深浅主题、自动识别浏览器语言(也可固定中文/English)、JSON 导入导出与本地存储说明。
- **Bing 每日一图**:桌面壁纸每日自动更新;由 Life OS 同源代理读取,避免浏览器跨域限制。 - **Bing 每日一图**:桌面壁纸每日自动更新;由 Life OS 同源代理读取,避免浏览器跨域限制。
- **Windows 式操作**:桌面端单击选择、双击打开;右键桌面或 App 图标可使用快捷菜单。 - **Windows 式操作**:桌面端单击选择、双击打开;右键桌面或 App 图标可使用快捷菜单。
- **快捷键与草稿恢复**:`Ctrl / ⌘ + K` 打开启动器,`Ctrl / ⌘ + F` 搜索记忆,`Ctrl / ⌘ + S` 归档 Today;当天未归档文字会自动恢复。 - **快捷键与草稿恢复**:`Ctrl / ⌘ + K` 打开启动器,`Ctrl / ⌘ + F` 搜索记忆,`Ctrl / ⌘ + S` 归档 Today;当天未归档文字会自动恢复。
- **Tasks 快捷键**:`Alt + 6` 打开目标与待办,`Alt + 7` 打开 System。
- **移动端**:App 以全屏方式打开,保留任务栏恢复入口。 - **移动端**:App 以全屏方式打开,保留任务栏恢复入口。
- **个人云端**:使用 ChatGPT 登录后,数据按账号隔离并同步到 SQLite 兼容的 D1 数据库。 - **个人云端**:使用 ChatGPT 登录后,数据按账号隔离并同步到 SQLite 兼容的 D1 数据库。
@@ -32,7 +34,7 @@
- `lifeos_settings` - `lifeos_settings`
- `lifeos_system` - `lifeos_system`
备份格式包含 `schemaVersion: 1`,为后续数据迁移预留兼容空间。 备份格式包含 `schemaVersion: 1`,为后续数据迁移预留兼容空间。目标和待办保存在 `lifeos_system.tasksBoard`,随现有云端 system 字段同步,并包含在新版 JSON 备份中。
## 运行 ## 运行
@@ -50,7 +52,8 @@ npm run validate
## 项目结构 ## 项目结构
```text ```text
public/index.html # 界面、样式与本地数据逻辑 public/index.html # 桌面框架与窗口交互
public/tasks.js # 目标与待办 App
public/addons.js # 窗口与辅助交互 public/addons.js # 窗口与辅助交互
worker/index.js # ChatGPT 登录门槛、云端数据 API 与 Bing 壁纸代理 worker/index.js # ChatGPT 登录门槛、云端数据 API 与 Bing 壁纸代理
drizzle/ # SQLite / D1 数据库迁移 drizzle/ # SQLite / D1 数据库迁移
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "life-os", "name": "life-os",
"version": "1.1.0", "version": "1.2.0",
"private": true, "private": true,
"type": "module", "type": "module",
"scripts": { "scripts": {
+12 -5
View File
@@ -1,8 +1,8 @@
/* Life OS reliability and productivity add-ons. */ /* Life OS reliability and productivity add-ons. */
(() => { (() => {
const DRAFT_KEY = 'lifeos_today_draft_v1'; const DRAFT_KEY = 'lifeos_today_draft_v1';
const APP_VERSION = '1.1.0'; const APP_VERSION = '1.2.0';
const appIds = ['today', 'timeline', 'museum', 'insights', 'profile', 'system']; const appIds = ['today', 'timeline', 'museum', 'insights', 'profile', 'tasks', 'system'];
const t = (key, values) => LifeI18n.t(key, values); const t = (key, values) => LifeI18n.t(key, values);
function readDraft() { function readDraft() {
@@ -75,7 +75,7 @@
}); });
} }
function exportData() { function exportData() {
const data = { app: 'Life OS', version: APP_VERSION, schemaVersion: 1, exportedAt: new Date().toISOString(), profile: profile(), memories: mems(), museum: museum(), settings: settings() }; const data = { app: 'Life OS', version: APP_VERSION, schemaVersion: 1, exportedAt: new Date().toISOString(), profile: profile(), memories: mems(), museum: museum(), settings: settings(), system: sys() };
const link = document.createElement('a'); const link = document.createElement('a');
link.href = URL.createObjectURL(new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' })); link.href = URL.createObjectURL(new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' }));
link.download = `life-os-backup-${day()}.json`; link.click(); link.download = `life-os-backup-${day()}.json`; link.click();
@@ -89,8 +89,15 @@
try { return [field, JSON.parse(localStorage.getItem(key) || JSON.stringify(cloudFallback[field]))]; } catch { return [field, cloudFallback[field]]; } try { return [field, JSON.parse(localStorage.getItem(key) || JSON.stringify(cloudFallback[field]))]; } catch { return [field, cloudFallback[field]]; }
})); }));
} }
function hasLocalLife(state) { return state.memories.length || state.museum.length || Object.keys(state.profile).length || Object.keys(state.settings).length > 2; } function hasLocalLife(state) { return state.memories.length || state.museum.length || Object.keys(state.profile).length || Object.keys(state.settings).length > 2 || state.system?.tasksBoard?.goals?.length || state.system?.tasksBoard?.tasks?.length; }
function applyCloudState(state) { function applyCloudState(state) {
let localSystem = {};
try { localSystem = JSON.parse(localStorage.getItem('lifeos_system') || '{}'); } catch {}
const localBoard = localSystem.tasksBoard, cloudBoard = state.system?.tasksBoard;
if ((localBoard?.goals?.length || localBoard?.tasks?.length) && state.system) {
const mergeById = (cloudItems = [], localItems = []) => [...cloudItems, ...localItems.filter(item => !cloudItems.some(existing => existing.id === item.id))];
state = { ...state, system: { ...state.system, tasksBoard: { goals: mergeById(cloudBoard?.goals, localBoard.goals), tasks: mergeById(cloudBoard?.tasks, localBoard.tasks) } } };
}
applyingCloud = true; applyingCloud = true;
Object.entries(CLOUD_KEYS).forEach(([field, key]) => localStorage.setItem(key, JSON.stringify(state[field] ?? cloudFallback[field]))); Object.entries(CLOUD_KEYS).forEach(([field, key]) => localStorage.setItem(key, JSON.stringify(state[field] ?? cloudFallback[field])));
applyingCloud = false; applyingCloud = false;
@@ -156,7 +163,7 @@
if (mod && e.key.toLowerCase() === 'k') { e.preventDefault(); toggleLauncher(); } if (mod && e.key.toLowerCase() === 'k') { e.preventDefault(); toggleLauncher(); }
if (mod && e.key.toLowerCase() === 'f') { e.preventDefault(); openSearch(); } if (mod && e.key.toLowerCase() === 'f') { e.preventDefault(); openSearch(); }
if (mod && e.key.toLowerCase() === 's' && document.querySelector('#entry')) { e.preventDefault(); archiveToday(); } if (mod && e.key.toLowerCase() === 's' && document.querySelector('#entry')) { e.preventDefault(); archiveToday(); }
if (e.altKey && /^[1-6]$/.test(e.key)) { e.preventDefault(); openApp(appIds[Number(e.key) - 1]); } if (e.altKey && /^[1-7]$/.test(e.key)) { e.preventDefault(); openApp(appIds[Number(e.key) - 1]); }
if (e.key === 'Escape') { closeSearch(); document.querySelector('#launcher')?.classList.add('hidden'); document.querySelector('#calendar')?.classList.add('hidden'); document.querySelector('#life-context')?.remove(); } if (e.key === 'Escape') { closeSearch(); document.querySelector('#launcher')?.classList.add('hidden'); document.querySelector('#calendar')?.classList.add('hidden'); document.querySelector('#life-context')?.remove(); }
}); });
})(); })();
Loaded 3 of 10 files, more files were not shown because too many files have changed in this diff. Show more