Compare commits
No files matched your search
@@ -8,15 +8,16 @@
|
|||||||
|
|
||||||
- **桌面式交互**:桌面图标、任务栏、启动器、多窗口、最小化/最大化/关闭。
|
- **桌面式交互**:桌面图标、任务栏、启动器、多窗口、最小化/最大化/关闭。
|
||||||
- **Today**:每日文字记录、情绪天气、1–5 能量等级,以及随机生成的“今日电影”。
|
- **Today**:每日文字记录、情绪天气、1–5 能量等级,以及随机生成的“今日电影”。
|
||||||
- **Timeline**:按时间轴浏览、编辑和删除记忆。
|
- **Timeline**:按时间轴浏览、编辑和删除记忆;支持标签、地点/场景、收藏和关联待办,并可按标签或关键词筛选。
|
||||||
- **Museum**:将重要记忆长期珍藏为馆藏。
|
- **Museum**:将重要记忆长期珍藏为馆藏。
|
||||||
- **Tasks / Goals**:创建目标和待办,关联任务、设置截止日期与优先级,查看今天/即将开始/已完成事项并追踪目标进度。
|
- **Tasks / Goals**:创建目标和待办,关联任务、设置截止日期与优先级,查看今天/即将开始/已完成事项并追踪目标进度。
|
||||||
|
- **Calendar**:月历查看每天的记忆、待办截止与完成记录,以及目标创建、目标日期和完成节点;可切换到日视图查看当天详情。
|
||||||
- **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 快捷键**:`Alt + 6` 打开目标与待办,`Alt + 7` 打开日历,`Alt + 8` 打开 System。
|
||||||
- **移动端**:App 以全屏方式打开,保留任务栏恢复入口。
|
- **移动端**:App 以全屏方式打开,保留任务栏恢复入口。
|
||||||
- **个人云端**:使用 ChatGPT 登录后,数据按账号隔离并同步到 SQLite 兼容的 D1 数据库。
|
- **个人云端**:使用 ChatGPT 登录后,数据按账号隔离并同步到 SQLite 兼容的 D1 数据库。
|
||||||
|
|
||||||
@@ -34,7 +35,7 @@
|
|||||||
- `lifeos_settings`
|
- `lifeos_settings`
|
||||||
- `lifeos_system`
|
- `lifeos_system`
|
||||||
|
|
||||||
备份格式包含 `schemaVersion: 1`,为后续数据迁移预留兼容空间。目标和待办保存在 `lifeos_system.tasksBoard`,随现有云端 system 字段同步,并包含在新版 JSON 备份中。
|
JSON 备份使用 `schemaVersion: 2`,导入仍兼容 v1 备份。D1 表结构保持 v1:目标和待办保存在 `lifeos_system.tasksBoard`,随现有 `system_json` 字段同步;记忆及其标签、地点、关联待办保存在 `lifeos_memories`。云端恢复时会把本机尚未同步的目标与待办合并并补传。旧版记忆继续兼容,新增信息为选填。
|
||||||
|
|
||||||
## 运行
|
## 运行
|
||||||
|
|
||||||
@@ -54,6 +55,7 @@ npm run validate
|
|||||||
```text
|
```text
|
||||||
public/index.html # 桌面框架与窗口交互
|
public/index.html # 桌面框架与窗口交互
|
||||||
public/tasks.js # 目标与待办 App
|
public/tasks.js # 目标与待办 App
|
||||||
|
public/calendar.js # 记忆、待办与目标节点的日历时间轴
|
||||||
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
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "life-os",
|
"name": "life-os",
|
||||||
"version": "1.2.0",
|
"version": "1.5.8",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
+33
-28
@@ -1,19 +1,22 @@
|
|||||||
/* 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.2.0';
|
const localDay = () => { const d = new Date(); return `${d.getFullYear()}-${String(d.getMonth()+1).padStart(2,'0')}-${String(d.getDate()).padStart(2,'0')}`; };
|
||||||
const appIds = ['today', 'timeline', 'museum', 'insights', 'profile', 'tasks', 'system'];
|
const escapeHtml = value => String(value || '').replace(/[&<>]/g, char => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[char]));
|
||||||
|
const APP_VERSION = '1.5.8';
|
||||||
|
window.LifeOSVersion = APP_VERSION;
|
||||||
|
const appIds = ['today', 'timeline', 'museum', 'insights', 'profile', 'tasks', 'calendar', 'system'];
|
||||||
const t = (key, values) => LifeI18n.t(key, values);
|
const t = (key, values) => LifeI18n.t(key, values);
|
||||||
|
|
||||||
function readDraft() {
|
function readDraft() {
|
||||||
try { return JSON.parse(localStorage.getItem(DRAFT_KEY)); } catch { return null; }
|
try { return JSON.parse(localStorage.getItem(DRAFT_KEY)); } catch { return null; }
|
||||||
}
|
}
|
||||||
function saveDraft(text) {
|
function saveDraft(text) {
|
||||||
localStorage.setItem(DRAFT_KEY, JSON.stringify({ date: new Date().toISOString().slice(0, 10), text, updatedAt: new Date().toISOString() }));
|
localStorage.setItem(DRAFT_KEY, JSON.stringify({ date: (typeof todayTargetDate !== 'undefined' && todayTargetDate) || localDay(), text, updatedAt: new Date().toISOString() }));
|
||||||
}
|
}
|
||||||
function restoreDraft(entry) {
|
function restoreDraft(entry) {
|
||||||
const draft = readDraft();
|
const draft = readDraft();
|
||||||
const today = new Date().toISOString().slice(0, 10);
|
const today = (typeof todayTargetDate !== 'undefined' && todayTargetDate) || localDay();
|
||||||
if (!draft || draft.date !== today || !draft.text || entry.value) return;
|
if (!draft || draft.date !== today || !draft.text || entry.value) return;
|
||||||
entry.value = draft.text;
|
entry.value = draft.text;
|
||||||
entry.dispatchEvent(new Event('input', { bubbles: true }));
|
entry.dispatchEvent(new Event('input', { bubbles: true }));
|
||||||
@@ -29,7 +32,7 @@
|
|||||||
function wireLanguageControl() { /* The System renderer owns this control. */ }
|
function wireLanguageControl() { /* The System renderer owns this control. */ }
|
||||||
function clearDraftAfterSave() {
|
function clearDraftAfterSave() {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
const today = new Date().toISOString().slice(0, 10);
|
const today = (typeof todayTargetDate !== 'undefined' && todayTargetDate) || localDay();
|
||||||
try {
|
try {
|
||||||
const saved = JSON.parse(localStorage.getItem('lifeos_memories') || '[]').some(m => m.date === today);
|
const saved = JSON.parse(localStorage.getItem('lifeos_memories') || '[]').some(m => m.date === today);
|
||||||
if (saved) localStorage.removeItem(DRAFT_KEY);
|
if (saved) localStorage.removeItem(DRAFT_KEY);
|
||||||
@@ -42,7 +45,7 @@
|
|||||||
closeSearch();
|
closeSearch();
|
||||||
const box = document.createElement('div');
|
const box = document.createElement('div');
|
||||||
box.id = 'lifeos-search';
|
box.id = 'lifeos-search';
|
||||||
box.innerHTML = `<div class="lifeos-search-backdrop"></div><section class="lifeos-search-panel"><div class="lifeos-search-head"><span>${t('addon.searchTitle')}</span><button aria-label="${t('common.close')}">×</button></div><input autofocus placeholder="${t('addon.searchPlaceholder')}"><div class="lifeos-search-results"></div><p class="lifeos-search-hint">${t('addon.searchHint')}</p></section>`;
|
box.innerHTML = `<div class="lifeos-search-backdrop"></div><section class="lifeos-search-panel"><div class="lifeos-search-head"><span>${t('addon.searchTitle')}</span><button aria-label="${t('common.close')}">×</button></div><input autofocus aria-label="${t('addon.searchPlaceholder')}" placeholder="${t('addon.searchPlaceholder')}"><div class="lifeos-search-results"></div><p class="lifeos-search-hint">${t('addon.searchHint')}</p></section>`;
|
||||||
document.body.append(box);
|
document.body.append(box);
|
||||||
const input = box.querySelector('input');
|
const input = box.querySelector('input');
|
||||||
const results = box.querySelector('.lifeos-search-results');
|
const results = box.querySelector('.lifeos-search-results');
|
||||||
@@ -50,8 +53,8 @@
|
|||||||
let memories = [];
|
let memories = [];
|
||||||
try { memories = JSON.parse(localStorage.getItem('lifeos_memories') || '[]'); } catch {}
|
try { memories = JSON.parse(localStorage.getItem('lifeos_memories') || '[]'); } catch {}
|
||||||
const query = input.value.trim().toLowerCase();
|
const query = input.value.trim().toLowerCase();
|
||||||
const found = memories.filter(m => !query || [m.filmTitle, m.text, m.mood, m.date].join(' ').toLowerCase().includes(query)).slice(0, 8);
|
const found = memories.filter(m => !query || [m.title, m.filmTitle, m.text, m.mood, m.date].join(' ').toLowerCase().includes(query)).slice(0, 8);
|
||||||
results.innerHTML = found.length ? found.map(m => `<button data-id="${m.id}"><small>${m.date} · ${t(`weather.${m.mood || 'cloudy'}`).toUpperCase()}</small><strong>${m.filmTitle || t('addon.untitled')}</strong><span>${(m.text || t('timeline.noNote')).replace(/[&<>]/g, c => ({'&':'&','<':'<','>':'>'}[c]))}</span></button>`).join('') : `<div class="lifeos-search-empty">${t('addon.noResults')}</div>`;
|
results.innerHTML = found.length ? found.map(m => `<button data-id="${m.id}" aria-label="${t('addon.openMemory',{title:escapeHtml(m.title || m.text?.trim().split('\n')[0].slice(0, 64) || t('timeline.untitled'))})}"><small>${LifeI18n.date(m.date,{dateStyle:'medium'})} · ${t(`weather.${m.mood || 'cloudy'}`).toUpperCase()}</small><strong>${escapeHtml(m.title || m.text?.trim().split('\n')[0].slice(0, 64) || t('timeline.untitled'))}</strong><span>${(m.text || t('timeline.noNote')).replace(/[&<>]/g, c => ({'&':'&','<':'<','>':'>'}[c]))}</span></button>`).join('') : `<div class="lifeos-search-empty">${t('addon.noResults')}</div>`;
|
||||||
results.querySelectorAll('button').forEach(button => button.addEventListener('click', () => { closeSearch(); openApp('timeline'); setTimeout(() => detail(button.dataset.id), 20); }));
|
results.querySelectorAll('button').forEach(button => button.addEventListener('click', () => { closeSearch(); openApp('timeline'); setTimeout(() => detail(button.dataset.id), 20); }));
|
||||||
};
|
};
|
||||||
input.addEventListener('input', render);
|
input.addEventListener('input', render);
|
||||||
@@ -63,45 +66,46 @@
|
|||||||
const style = document.createElement('style');
|
const style = document.createElement('style');
|
||||||
style.textContent = `.lifeos-search-backdrop{position:fixed;inset:0;background:#0008;z-index:8000}.lifeos-search-panel{position:fixed;z-index:8001;top:18vh;left:50%;transform:translateX(-50%);width:min(560px,calc(100vw - 28px));background:var(--panel);border:1px solid var(--line);border-radius:15px;box-shadow:var(--shadow);padding:15px}.lifeos-search-head{display:flex;justify-content:space-between;align-items:center;font:500 11px 'DM Mono';letter-spacing:.12em;color:var(--muted);margin-bottom:13px}.lifeos-search-head button{border:0;background:transparent;color:var(--ink);font-size:20px}.lifeos-search-panel input{width:100%;background:var(--panel2);border:1px solid var(--line);border-radius:9px;padding:12px;outline:none}.lifeos-search-panel input:focus{border-color:var(--accent)}.lifeos-search-results{margin-top:9px;max-height:360px;overflow:auto}.lifeos-search-results button{display:grid;gap:3px;width:100%;text-align:left;border:0;border-radius:9px;padding:11px;background:transparent;color:var(--ink)}.lifeos-search-results button:hover{background:var(--panel2)}.lifeos-search-results small,.lifeos-search-results span,.lifeos-search-hint{color:var(--muted);font-size:12px}.lifeos-search-results strong{font-size:14px}.lifeos-search-results span{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.lifeos-search-empty{padding:24px;color:var(--muted);text-align:center;font-size:13px}.lifeos-search-hint{margin:11px 0 0;font:10px 'DM Mono';letter-spacing:.04em}.launchitem svg{width:28px;height:28px;display:block;margin:auto;fill:none;stroke:currentColor;stroke-width:1.7;stroke-linecap:round;stroke-linejoin:round}.launchitem:hover svg{color:var(--accent)}`;
|
style.textContent = `.lifeos-search-backdrop{position:fixed;inset:0;background:#0008;z-index:8000}.lifeos-search-panel{position:fixed;z-index:8001;top:18vh;left:50%;transform:translateX(-50%);width:min(560px,calc(100vw - 28px));background:var(--panel);border:1px solid var(--line);border-radius:15px;box-shadow:var(--shadow);padding:15px}.lifeos-search-head{display:flex;justify-content:space-between;align-items:center;font:500 11px 'DM Mono';letter-spacing:.12em;color:var(--muted);margin-bottom:13px}.lifeos-search-head button{border:0;background:transparent;color:var(--ink);font-size:20px}.lifeos-search-panel input{width:100%;background:var(--panel2);border:1px solid var(--line);border-radius:9px;padding:12px;outline:none}.lifeos-search-panel input:focus{border-color:var(--accent)}.lifeos-search-results{margin-top:9px;max-height:360px;overflow:auto}.lifeos-search-results button{display:grid;gap:3px;width:100%;text-align:left;border:0;border-radius:9px;padding:11px;background:transparent;color:var(--ink)}.lifeos-search-results button:hover{background:var(--panel2)}.lifeos-search-results small,.lifeos-search-results span,.lifeos-search-hint{color:var(--muted);font-size:12px}.lifeos-search-results strong{font-size:14px}.lifeos-search-results span{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.lifeos-search-empty{padding:24px;color:var(--muted);text-align:center;font-size:13px}.lifeos-search-hint{margin:11px 0 0;font:10px 'DM Mono';letter-spacing:.04em}.launchitem svg{width:28px;height:28px;display:block;margin:auto;fill:none;stroke:currentColor;stroke-width:1.7;stroke-linecap:round;stroke-linejoin:round}.launchitem:hover svg{color:var(--accent)}`;
|
||||||
document.head.append(style);
|
document.head.append(style);
|
||||||
function polishSystemChrome() {
|
|
||||||
document.querySelector('#desktop')?.removeAttribute('title');
|
|
||||||
document.querySelectorAll('.systemline').forEach(node => {
|
|
||||||
const next = node.textContent.replace(/LIFE OS v0\.1/g, `LIFE OS v${APP_VERSION}`);
|
|
||||||
if (next !== node.textContent) node.textContent = next;
|
|
||||||
});
|
|
||||||
document.querySelectorAll('#content-system .settings section:last-child p').forEach(node => {
|
|
||||||
const next = node.innerHTML.replace(/Version 0\.1/g, `Version ${APP_VERSION}`).replace(/版本 0\.1/g, `版本 ${APP_VERSION}`);
|
|
||||||
if (next !== node.innerHTML) node.innerHTML = next;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
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(), 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');
|
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();
|
||||||
const state = settings(); state.lastBackup = new Date().toISOString(); save('lifeos_settings', state); renderAll(); toast('feedback.backup');
|
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' };
|
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;
|
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() {
|
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]]; }
|
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 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 = {};
|
let localSystem = {};
|
||||||
try { localSystem = JSON.parse(localStorage.getItem('lifeos_system') || '{}'); } catch {}
|
try { localSystem = JSON.parse(localStorage.getItem('lifeos_system') || '{}'); } catch {}
|
||||||
const localBoard = localSystem.tasksBoard, cloudBoard = state.system?.tasksBoard;
|
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) {
|
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) } } };
|
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;
|
||||||
todayDraft = null; renderIcons(); renderAll(); applyLanguage();
|
todayDraft = null; window.renderIcons?.(); window.renderAll?.(); window.applyLanguage?.();
|
||||||
|
return mergedLocalBoard || systemNeedsMigration;
|
||||||
}
|
}
|
||||||
async function uploadCloudState() {
|
async function uploadCloudState() {
|
||||||
if (!cloudReady) return;
|
if (!cloudReady) return;
|
||||||
@@ -131,7 +135,9 @@
|
|||||||
const local = cloudSnapshot();
|
const local = cloudSnapshot();
|
||||||
if (state) {
|
if (state) {
|
||||||
if (hasLocalLife(local)) localStorage.setItem('lifeos_precloud_backup_v1', JSON.stringify({ exportedAt: new Date().toISOString(), state: local }));
|
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)) {
|
} else if (hasLocalLife(local)) {
|
||||||
migrationPrompt(local);
|
migrationPrompt(local);
|
||||||
} else {
|
} else {
|
||||||
@@ -150,10 +156,9 @@
|
|||||||
cloudStyle.textContent = `#lifeos-cloud-migration{position:fixed;inset:0;z-index:9000;background:#071008b8;display:grid;place-items:center;padding:20px}.lifeos-cloud-card{width:min(470px,100%);background:var(--panel);border:1px solid var(--line);border-radius:16px;padding:25px;box-shadow:var(--shadow)}.lifeos-cloud-card h2{margin:10px 0;font-size:25px;letter-spacing:-.04em}.lifeos-cloud-card p{color:var(--muted);line-height:1.65;margin:0 0 20px}`;
|
cloudStyle.textContent = `#lifeos-cloud-migration{position:fixed;inset:0;z-index:9000;background:#071008b8;display:grid;place-items:center;padding:20px}.lifeos-cloud-card{width:min(470px,100%);background:var(--panel);border:1px solid var(--line);border-radius:16px;padding:25px;box-shadow:var(--shadow)}.lifeos-cloud-card h2{margin:10px 0;font-size:25px;letter-spacing:-.04em}.lifeos-cloud-card p{color:var(--muted);line-height:1.65;margin:0 0 20px}`;
|
||||||
document.head.append(cloudStyle);
|
document.head.append(cloudStyle);
|
||||||
startCloudSync();
|
startCloudSync();
|
||||||
new MutationObserver(() => { wireToday(); wireLanguageControl(); polishSystemChrome(); }).observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['title'] });
|
new MutationObserver(() => { wireToday(); wireLanguageControl(); }).observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['title'] });
|
||||||
wireToday();
|
wireToday();
|
||||||
wireLanguageControl();
|
wireLanguageControl();
|
||||||
polishSystemChrome();
|
|
||||||
document.addEventListener('click', e => { if (e.target.closest('#content-today .primary')) clearDraftAfterSave(); });
|
document.addEventListener('click', e => { if (e.target.closest('#content-today .primary')) clearDraftAfterSave(); });
|
||||||
document.addEventListener('click', e => {
|
document.addEventListener('click', e => {
|
||||||
if (e.target.closest('.launchitem')) document.querySelector('#launcher')?.classList.add('hidden');
|
if (e.target.closest('.launchitem')) document.querySelector('#launcher')?.classList.add('hidden');
|
||||||
@@ -163,7 +168,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-7]$/.test(e.key)) { e.preventDefault(); openApp(appIds[Number(e.key) - 1]); }
|
if (e.altKey && /^[1-8]$/.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 15 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user