Files
Life-OS/dist/addons.js
T

93 lines
7.6 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* Life OS reliability and productivity add-ons. */
(() => {
const DRAFT_KEY = 'lifeos_today_draft_v1';
const appIds = ['today', 'timeline', 'museum', 'insights', 'profile', 'system'];
const zh = () => (typeof lang === 'function' ? lang() : 'en') === 'zh';
const copy = (en, cn) => (zh() ? cn : en);
function readDraft() {
try { return JSON.parse(localStorage.getItem(DRAFT_KEY)); } catch { return null; }
}
function saveDraft(text) {
localStorage.setItem(DRAFT_KEY, JSON.stringify({ date: new Date().toISOString().slice(0, 10), text, updatedAt: new Date().toISOString() }));
}
function restoreDraft(entry) {
const draft = readDraft();
const today = new Date().toISOString().slice(0, 10);
if (!draft || draft.date !== today || !draft.text || entry.value) return;
entry.value = draft.text;
entry.dispatchEvent(new Event('input', { bubbles: true }));
if (typeof toast === 'function') toast(copy('DRAFT RESTORED', '草稿已恢复'));
}
function wireToday() {
const entry = document.querySelector('#entry');
if (!entry || entry.dataset.lifeosDraft === 'ready') return;
entry.dataset.lifeosDraft = 'ready';
restoreDraft(entry);
entry.addEventListener('input', () => saveDraft(entry.value));
}
function wireLanguageControl() {
const settingsPanel = document.querySelector('#content-system .settings');
if (!settingsPanel || settingsPanel.querySelector('#lifeos-browser-language')) return;
const state = settings();
const actual = typeof browserLanguage === 'function' ? browserLanguage() : 'en';
const section = document.createElement('section');
section.id = 'lifeos-browser-language';
section.innerHTML = `<div class="eyebrow">${copy('BROWSER LANGUAGE', '浏览器语言')}</div><p class="sub">${copy(`Detected: ${actual === 'zh' ? 'Chinese' : 'English'}`, `当前识别:${actual === 'zh' ? '中文' : 'English'}`)}</p><div class="choices"><button class="chip ${state.languageMode !== 'manual' ? 'selected' : ''}" data-language="auto">${copy('AUTO', '跟随浏览器')}</button><button class="chip ${state.languageMode === 'manual' && state.language === 'en' ? 'selected' : ''}" data-language="en">ENGLISH</button><button class="chip ${state.languageMode === 'manual' && state.language === 'zh' ? 'selected' : ''}" data-language="zh">中文</button></div>`;
section.querySelectorAll('button').forEach(button => button.addEventListener('click', () => setLanguage(button.dataset.language)));
[...settingsPanel.children].forEach(node => {
const label = node.querySelector?.('.eyebrow')?.textContent.trim();
if (label === 'LANGUAGE' || label === '语言') node.remove();
});
settingsPanel.prepend(section);
}
function clearDraftAfterSave() {
setTimeout(() => {
const today = new Date().toISOString().slice(0, 10);
try {
const saved = JSON.parse(localStorage.getItem('lifeos_memories') || '[]').some(m => m.date === today);
if (saved) localStorage.removeItem(DRAFT_KEY);
} catch {}
}, 700);
}
function closeSearch() { document.querySelector('#lifeos-search')?.remove(); }
function openSearch() {
closeSearch();
const box = document.createElement('div');
box.id = 'lifeos-search';
box.innerHTML = `<div class="lifeos-search-backdrop"></div><section class="lifeos-search-panel"><div class="lifeos-search-head"><span>${copy('SEARCH MEMORY ARCHIVE', '搜索记忆档案')}</span><button aria-label="Close">×</button></div><input autofocus placeholder="${copy('Search titles, notes, moods…', '搜索标题、记录或情绪…')}"><div class="lifeos-search-results"></div><p class="lifeos-search-hint">${copy('ESC to close · click a result to open it', 'ESC 关闭 · 点击结果即可打开')}</p></section>`;
document.body.append(box);
const input = box.querySelector('input');
const results = box.querySelector('.lifeos-search-results');
const render = () => {
let memories = [];
try { memories = JSON.parse(localStorage.getItem('lifeos_memories') || '[]'); } catch {}
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);
results.innerHTML = found.length ? found.map(m => `<button data-id="${m.id}"><small>${m.date} · ${(m.mood || '').toUpperCase()}</small><strong>${m.filmTitle || copy('Untitled memory', '未命名记忆')}</strong><span>${(m.text || copy('No written note.', '未写下记录。')).replace(/[&<>]/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;'}[c]))}</span></button>`).join('') : `<div class="lifeos-search-empty">${copy('No memories found.', '没有找到记忆。')}</div>`;
results.querySelectorAll('button').forEach(button => button.addEventListener('click', () => { closeSearch(); openApp('timeline'); setTimeout(() => detail(button.dataset.id), 20); }));
};
input.addEventListener('input', render);
box.querySelector('.lifeos-search-head button').addEventListener('click', closeSearch);
box.querySelector('.lifeos-search-backdrop').addEventListener('click', closeSearch);
render(); input.focus();
}
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}`;
document.head.append(style);
new MutationObserver(() => { wireToday(); wireLanguageControl(); }).observe(document.body, { childList: true, subtree: true });
wireToday();
wireLanguageControl();
document.addEventListener('click', e => { if (e.target.closest('#content-today .primary')) clearDraftAfterSave(); });
document.addEventListener('keydown', e => {
const mod = e.ctrlKey || e.metaKey;
if (mod && e.key.toLowerCase() === 'k') { e.preventDefault(); toggleLauncher(); }
if (mod && e.key.toLowerCase() === 'f') { e.preventDefault(); openSearch(); }
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.key === 'Escape') { closeSearch(); document.querySelector('#launcher')?.classList.add('hidden'); document.querySelector('#calendar')?.classList.add('hidden'); document.querySelector('#life-context')?.remove(); }
});
})();