Compare commits
No files matched your search
@@ -8,7 +8,7 @@
|
||||
|
||||
- **桌面式交互**:桌面图标、任务栏、启动器、多窗口、最小化/最大化/关闭。
|
||||
- **Today**:每日文字记录、情绪天气、1–5 能量等级,以及随机生成的“今日电影”。
|
||||
- **Timeline**:按时间轴浏览、编辑和删除记忆。
|
||||
- **Timeline**:按时间轴浏览、编辑和删除记忆;支持标签、地点/场景、收藏和关联待办,并可按标签或关键词筛选。
|
||||
- **Museum**:将重要记忆长期珍藏为馆藏。
|
||||
- **Tasks / Goals**:创建目标和待办,关联任务、设置截止日期与优先级,查看今天/即将开始/已完成事项并追踪目标进度。
|
||||
- **Calendar**:月历查看每天的记忆、待办截止与完成记录,以及目标创建、目标日期和完成节点;可切换到日视图查看当天详情。
|
||||
@@ -35,7 +35,7 @@
|
||||
- `lifeos_settings`
|
||||
- `lifeos_system`
|
||||
|
||||
备份格式包含 `schemaVersion: 1`,为后续数据迁移预留兼容空间。目标和待办保存在 `lifeos_system.tasksBoard`,随现有云端 system 字段同步,并包含在新版 JSON 备份中。
|
||||
JSON 备份使用 `schemaVersion: 2`,导入仍兼容 v1 备份。D1 表结构保持 v1:目标和待办保存在 `lifeos_system.tasksBoard`,随现有 `system_json` 字段同步;记忆及其标签、地点、关联待办保存在 `lifeos_memories`。云端恢复时会把本机尚未同步的目标与待办合并并补传。旧版记忆继续兼容,新增信息为选填。
|
||||
|
||||
## 运行
|
||||
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "life-os",
|
||||
"version": "1.3.0",
|
||||
"version": "1.4.2",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
|
||||
+19
-6
@@ -1,7 +1,7 @@
|
||||
/* Life OS reliability and productivity add-ons. */
|
||||
(() => {
|
||||
const DRAFT_KEY = 'lifeos_today_draft_v1';
|
||||
const APP_VERSION = '1.3.0';
|
||||
const APP_VERSION = '1.4.2';
|
||||
const appIds = ['today', 'timeline', 'museum', 'insights', 'profile', 'tasks', 'calendar', 'system'];
|
||||
const t = (key, values) => LifeI18n.t(key, values);
|
||||
|
||||
@@ -75,33 +75,44 @@
|
||||
});
|
||||
}
|
||||
function exportData() {
|
||||
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 data = { app: 'Life OS', version: APP_VERSION, schemaVersion: 2, exportedAt: new Date().toISOString(), profile: profile(), memories: mems(), museum: museum(), settings: settings(), system: sys() };
|
||||
const link = document.createElement('a');
|
||||
link.href = URL.createObjectURL(new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' }));
|
||||
link.download = `life-os-backup-${day()}.json`; link.click();
|
||||
const state = settings(); state.lastBackup = new Date().toISOString(); save('lifeos_settings', state); renderAll(); toast('feedback.backup');
|
||||
}
|
||||
// The System panel uses inline onclick handlers, so expose this complete exporter.
|
||||
window.exportData = exportData;
|
||||
const CLOUD_KEYS = { profile: 'lifeos_profile', memories: 'lifeos_memories', museum: 'lifeos_museum', settings: 'lifeos_settings', system: 'lifeos_system' };
|
||||
let cloudReady = false, applyingCloud = false, syncTimer = null, syncErrorShown = false;
|
||||
const cloudFallback = { profile: {}, memories: [], museum: [], settings: {}, system: {} };
|
||||
const cloudFallback = { profile: {}, memories: [], museum: [], settings: {}, system: { schemaVersion: 2 } };
|
||||
function cloudSnapshot() {
|
||||
return Object.fromEntries(Object.entries(CLOUD_KEYS).map(([field, key]) => {
|
||||
const snapshot = Object.fromEntries(Object.entries(CLOUD_KEYS).map(([field, key]) => {
|
||||
try { return [field, JSON.parse(localStorage.getItem(key) || JSON.stringify(cloudFallback[field]))]; } catch { return [field, cloudFallback[field]]; }
|
||||
}));
|
||||
snapshot.system = { ...snapshot.system, schemaVersion: 2 };
|
||||
return snapshot;
|
||||
}
|
||||
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) {
|
||||
let localSystem = {};
|
||||
try { localSystem = JSON.parse(localStorage.getItem('lifeos_system') || '{}'); } catch {}
|
||||
const localBoard = localSystem.tasksBoard, cloudBoard = state.system?.tasksBoard;
|
||||
const systemNeedsMigration = (Number(state.system?.schemaVersion) || 1) < 2;
|
||||
state = { ...state, system: { ...(state.system || {}), schemaVersion: 2 } };
|
||||
let mergedLocalBoard = false;
|
||||
if ((localBoard?.goals?.length || localBoard?.tasks?.length) && state.system) {
|
||||
const mergeById = (cloudItems = [], localItems = []) => [...cloudItems, ...localItems.filter(item => !cloudItems.some(existing => existing.id === item.id))];
|
||||
const mergeById = (cloudItems = [], localItems = []) => {
|
||||
mergedLocalBoard ||= localItems.some(item => !cloudItems.some(existing => existing.id === item.id));
|
||||
return [...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;
|
||||
Object.entries(CLOUD_KEYS).forEach(([field, key]) => localStorage.setItem(key, JSON.stringify(state[field] ?? cloudFallback[field])));
|
||||
applyingCloud = false;
|
||||
todayDraft = null; renderIcons(); renderAll(); applyLanguage();
|
||||
return mergedLocalBoard || systemNeedsMigration;
|
||||
}
|
||||
async function uploadCloudState() {
|
||||
if (!cloudReady) return;
|
||||
@@ -131,7 +142,9 @@
|
||||
const local = cloudSnapshot();
|
||||
if (state) {
|
||||
if (hasLocalLife(local)) localStorage.setItem('lifeos_precloud_backup_v1', JSON.stringify({ exportedAt: new Date().toISOString(), state: local }));
|
||||
applyCloudState(state); cloudReady = true; toast('addon.cloudRestored');
|
||||
const mergedLocalBoard = applyCloudState(state); cloudReady = true;
|
||||
if (mergedLocalBoard) await uploadCloudState();
|
||||
toast('addon.cloudRestored');
|
||||
} else if (hasLocalLife(local)) {
|
||||
migrationPrompt(local);
|
||||
} else {
|
||||
|
||||
Loaded 3 of 9 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user