feat(memory): add tags, context, favorites, and task links
This commit is contained in:
1 parent
f5e689d68b
commit
5fd997be3c
7 files changed
+35
-16
No files matched your search
+1
-1
@@ -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.0';
|
||||
const appIds = ['today', 'timeline', 'museum', 'insights', 'profile', 'tasks', 'calendar', 'system'];
|
||||
const t = (key, values) => LifeI18n.t(key, values);
|
||||
|
||||
|
||||
@@ -7,6 +7,9 @@
|
||||
const short = value => new Intl.DateTimeFormat(locale(), { month: 'short', day: 'numeric' }).format(new Date(value + 'T12:00:00')).toUpperCase();
|
||||
const weather = mood => t(`weather.${mood}`);
|
||||
const rootModal = () => document.getElementById('modal');
|
||||
const linkedTasks = () => sys().tasksBoard?.tasks || [];
|
||||
const tagList = value => [...new Set(String(value || '').split(/[,,]/).map(tag => tag.trim()).filter(Boolean))].slice(0, 12);
|
||||
const memoryMetaFields = memory => `<div class="memory-meta-form"><label>${t('memory.tags')}<input id="memory-tags" maxlength="180" value="${esc((memory?.tags || []).join(', '))}" placeholder="${t('memory.tagsPlaceholder')}"></label><label>${t('memory.place')}<input id="memory-place" maxlength="100" value="${esc(memory?.location || '')}" placeholder="${t('memory.placePlaceholder')}"></label><label>${t('memory.linkTask')}<select id="memory-task"><option value="">${t('memory.noTask')}</option>${linkedTasks().map(task => `<option value="${esc(task.id)}" ${task.id === memory?.taskId ? 'selected' : ''}>${esc(task.title)}</option>`).join('')}</select></label></div>`;
|
||||
|
||||
window.appName = id => t(`app.${id}`);
|
||||
window.moodName = weather;
|
||||
@@ -23,23 +26,34 @@
|
||||
const memory = mems().find(item => item.date === day());
|
||||
if (todayDraft === null) { selectedMood = memory?.mood || 'cloudy'; selectedEnergy = memory?.energy || 3; }
|
||||
const film = memory || filmFor(selectedMood, selectedEnergy), text = todayDraft === null ? (memory?.text || '') : todayDraft;
|
||||
container.innerHTML = `<div class="eyebrow">${weekday(day()).toUpperCase()}</div><h1>${uiDate(day())}</h1><p class="sub">${t('today.prompt')}</p><div class="today-grid"><div><textarea id="entry" class="entry" placeholder="${t('today.placeholder')}" oninput="todayDraft=this.value">${esc(text)}</textarea><div class="actions"><button class="primary" onclick="archiveToday()">${t(memory ? 'today.update' : 'today.archive')}</button><span id="saveStatus" class="status"></span></div></div><aside><div class="eyebrow">${t('today.weather')}</div><div class="choices">${['clear','light','cloudy','rain','storm'].map(item => `<button class="chip ${item === selectedMood ? 'selected' : ''}" onclick="setMood('${item}')">${weather(item)}</button>`).join('')}</div><div class="eyebrow" style="margin-top:22px">${t('today.energy')}</div><div class="energy">${[1,2,3,4,5].map(item => `<button class="${item === selectedEnergy ? 'selected' : ''}" onclick="setEnergy(${item})">${item}</button>`).join('')}</div><div class="film"><div class="eyebrow">${t('today.film')}</div><div class="film-title">${film.filmTitle}</div><div class="sub">${film.filmSubtitle}</div><p class="film-copy">“${film.filmLine}”</p></div></aside></div>`;
|
||||
container.innerHTML = `<div class="eyebrow">${weekday(day()).toUpperCase()}</div><h1>${uiDate(day())}</h1><p class="sub">${t('today.prompt')}</p><div class="today-grid"><div><textarea id="entry" class="entry" placeholder="${t('today.placeholder')}" oninput="todayDraft=this.value">${esc(text)}</textarea>${memoryMetaFields(memory)}<div class="actions"><button class="primary" onclick="archiveToday()">${t(memory ? 'today.update' : 'today.archive')}</button><span id="saveStatus" class="status"></span></div></div><aside><div class="eyebrow">${t('today.weather')}</div><div class="choices">${['clear','light','cloudy','rain','storm'].map(item => `<button class="chip ${item === selectedMood ? 'selected' : ''}" onclick="setMood('${item}')">${weather(item)}</button>`).join('')}</div><div class="eyebrow" style="margin-top:22px">${t('today.energy')}</div><div class="energy">${[1,2,3,4,5].map(item => `<button class="${item === selectedEnergy ? 'selected' : ''}" onclick="setEnergy(${item})">${item}</button>`).join('')}</div><div class="film"><div class="eyebrow">${t('today.film')}</div><div class="film-title">${film.filmTitle}</div><div class="sub">${film.filmSubtitle}</div><p class="film-copy">“${film.filmLine}”</p></div></aside></div>`;
|
||||
};
|
||||
window.archiveToday = () => {
|
||||
const text = document.querySelector('#entry')?.value.trim() || '', old = mems().find(item => item.date === day()), film = old || filmFor(selectedMood, selectedEnergy), now = new Date().toISOString();
|
||||
const memory = { id: old?.id || `memory_${crypto.randomUUID()}`, date: day(), text, mood: selectedMood, energy: selectedEnergy, filmTitle: film.filmTitle, filmSubtitle: film.filmSubtitle, filmLine: film.filmLine, createdAt: old?.createdAt || now, updatedAt: now };
|
||||
const memory = { ...old, id: old?.id || `memory_${crypto.randomUUID()}`, date: day(), text, mood: selectedMood, energy: selectedEnergy, tags: tagList(document.querySelector('#memory-tags')?.value), location: document.querySelector('#memory-place')?.value.trim() || '', taskId: document.querySelector('#memory-task')?.value || '', filmTitle: film.filmTitle, filmSubtitle: film.filmSubtitle, filmLine: film.filmLine, createdAt: old?.createdAt || now, updatedAt: now };
|
||||
const status = document.querySelector('#saveStatus'); status.textContent = t('today.archiving');
|
||||
setTimeout(() => { save('lifeos_memories', [...mems().filter(item => item.date !== day()), memory].sort((a, b) => b.date.localeCompare(a.date))); todayDraft = null; status.textContent = t('today.archived'); renderAll(); }, 360);
|
||||
};
|
||||
window.timeline = container => {
|
||||
const all = mems();
|
||||
container.innerHTML = `<div class="eyebrow">${t('timeline.eyebrow')}</div><h1>${lang() === 'zh' ? `${all.length} 条记忆已归档` : `${all.length} ${all.length === 1 ? 'MEMORY' : 'MEMORIES'} ARCHIVED`}</h1>${all.length ? `<div class="timeline">${all.map(memory => `<article class="memory"><button onclick="detail('${memory.id}')"><div class="date">${short(memory.date)} · ${weather(memory.mood).toUpperCase()} · ${t('common.energy')} ${memory.energy}</div><div class="mtitle">${esc(memory.filmTitle)}</div><div class="snippet">${esc(memory.text || t('timeline.noNote'))}</div></button></article>`).join('')}</div>` : `<div class="empty">${t('timeline.emptyTitle')}<br>${t('timeline.emptyBody')}</div>`}`;
|
||||
const all = mems(), tags = [...new Set(all.flatMap(item => item.tags || []))].sort((a,b) => a.localeCompare(b));
|
||||
const query = window.timelineQuery || '', selectedTag = window.timelineTag || '';
|
||||
const filtered = all.filter(memory => {
|
||||
const task = linkedTasks().find(item => item.id === memory.taskId);
|
||||
const haystack = [memory.filmTitle, memory.text, memory.location, ...(memory.tags || []), task?.title].join(' ').toLocaleLowerCase();
|
||||
return (!query || haystack.includes(query.toLocaleLowerCase())) && (!selectedTag || (memory.tags || []).includes(selectedTag));
|
||||
});
|
||||
const isSaved = memory => museum().some(item => item.memoryId === memory.id);
|
||||
container.innerHTML = `<div class="eyebrow">${t('timeline.eyebrow')}</div><h1>${t('timeline.heading',{count:filtered.length,total:all.length})}</h1><div class="timeline-tools"><label class="timeline-search"><span class="sr-only">${t('timeline.search')}</span><input type="search" value="${esc(query)}" placeholder="${t('timeline.searchPlaceholder')}" oninput="setTimelineQuery(this.value)"></label><label class="timeline-tag-filter"><span class="sr-only">${t('timeline.filterTag')}</span><select onchange="setTimelineTag(this.value)"><option value="">${t('timeline.allTags')}</option>${tags.map(tag => `<option value="${esc(tag)}" ${tag === selectedTag ? 'selected' : ''}>${esc(tag)}</option>`).join('')}</select></label></div>${filtered.length ? `<div class="timeline">${filtered.map(memory => { const task = linkedTasks().find(item => item.id === memory.taskId); return `<article class="memory"><button class="memory-open" onclick="detail('${esc(memory.id)}')"><div class="date">${short(memory.date)} · ${weather(memory.mood).toUpperCase()} · ${t('common.energy')} ${memory.energy}${isSaved(memory) ? ` · ★ ${t('timeline.favorite')}` : ''}</div><div class="mtitle">${esc(memory.filmTitle)}</div><div class="snippet">${esc(memory.text || t('timeline.noNote'))}</div><div class="memory-badges">${memory.location ? `<span>⌖ ${esc(memory.location)}</span>` : ''}${task ? `<span>↗ ${esc(task.title)}</span>` : ''}${(memory.tags || []).map(tag => `<span># ${esc(tag)}</span>`).join('')}</div></button></article>`; }).join('')}</div>` : `<div class="empty">${all.length ? t('timeline.noMatches') : `${t('timeline.emptyTitle')}<br>${t('timeline.emptyBody')}`}</div>`}`;
|
||||
};
|
||||
window.setTimelineQuery = value => { window.timelineQuery = value; const field = document.querySelector('#content-timeline input[type=search]'); const start = field?.selectionStart; timeline(document.querySelector('#content-timeline')); const next = document.querySelector('#content-timeline input[type=search]'); next?.focus(); if (start !== undefined) next?.setSelectionRange(start,start); };
|
||||
window.setTimelineTag = value => { window.timelineTag = value; timeline(document.querySelector('#content-timeline')); };
|
||||
window.detail = id => {
|
||||
const memory = mems().find(item => item.id === id); if (!memory) return;
|
||||
rootModal().innerHTML = `<div class="modal" onclick="if(event.target === this) closeModal()"><div class="modalbox" role="dialog" aria-modal="true" aria-labelledby="memory-detail-title"><button class="modal-close" type="button" onclick="closeModal()" aria-label="${t('common.close')}">×</button><div class="eyebrow">${t('timeline.detail')}</div><h1 id="memory-detail-title">${esc(memory.filmTitle)}</h1><p class="sub">${uiDate(memory.date)} · ${weather(memory.mood)} · ${t('common.energy')} ${memory.energy}</p><p style="line-height:1.65">${esc(memory.text || t('timeline.noNote')).replace(/\n/g,'<br>')}</p><p class="film-copy">${memory.filmSubtitle}</p><div class="actions"><button class="primary" onclick="editMemory('${memory.id}')">${t('common.edit')}</button><button class="secondary" onclick="preserve('${memory.id}')">${museum().some(item => item.memoryId === memory.id) ? t('timeline.preserved') : t('timeline.addMuseum')}</button><button class="danger" onclick="confirmDelete('${memory.id}')">${t('common.delete')}</button></div></div></div>`;
|
||||
const task = linkedTasks().find(item => item.id === memory.taskId), favorite = museum().some(item => item.memoryId === memory.id);
|
||||
rootModal().innerHTML = `<div class="modal" onclick="if(event.target === this) closeModal()"><div class="modalbox" role="dialog" aria-modal="true" aria-labelledby="memory-detail-title"><button class="modal-close" type="button" onclick="closeModal()" aria-label="${t('common.close')}">×</button><div class="eyebrow">${t('timeline.detail')}</div><h1 id="memory-detail-title">${esc(memory.filmTitle)}</h1><p class="sub">${uiDate(memory.date)} · ${weather(memory.mood)} · ${t('common.energy')} ${memory.energy}</p><p style="line-height:1.65">${esc(memory.text || t('timeline.noNote')).replace(/\n/g,'<br>')}</p>${memory.location ? `<p class="memory-detail-meta">⌖ ${esc(memory.location)}</p>` : ''}${task ? `<p class="memory-detail-meta">↗ ${esc(t('memory.relatedTask'))}: <button class="text-link" onclick="closeModal();openApp('tasks')">${esc(task.title)}</button></p>` : ''}${(memory.tags || []).length ? `<div class="memory-badges detail-badges">${memory.tags.map(tag => `<span># ${esc(tag)}</span>`).join('')}</div>` : ''}<p class="film-copy">${esc(memory.filmSubtitle || '')}</p><div class="actions"><button class="primary" onclick="editMemory('${esc(memory.id)}')">${t('common.edit')}</button><button class="secondary" onclick="preserve('${esc(memory.id)}')">${favorite ? t('timeline.preserved') : t('memory.favorite')}</button><button class="danger" onclick="confirmDelete('${esc(memory.id)}')">${t('common.delete')}</button></div></div></div>`;
|
||||
};
|
||||
window.editMemory = id => { const memory = mems().find(item => item.id === id); rootModal().innerHTML = ''; openApp('today'); selectedMood = memory.mood; selectedEnergy = memory.energy; const all = mems(), index = all.findIndex(item => item.id === id); all[index].date = day(); save('lifeos_memories', all); today(document.querySelector('#content-today')); document.querySelector('#entry').value = memory.text; toast(t('timeline.editing')); };
|
||||
window.editMemory = id => { const memory = mems().find(item => item.id === id); if (!memory) return; rootModal().innerHTML = `<div class="modal" onclick="if(event.target===this) closeModal()"><form class="modalbox memory-edit-form" onsubmit="saveMemoryEdit(event,'${esc(memory.id)}')" role="dialog" aria-modal="true" aria-labelledby="memory-edit-title"><button type="button" class="modal-close" onclick="closeModal()" aria-label="${t('common.close')}">×</button><div class="eyebrow">${t('timeline.detail')}</div><h2 id="memory-edit-title">${t('memory.edit')}</h2><p class="sub">${uiDate(memory.date)}</p><label>${t('memory.note')}<textarea id="memory-edit-note" rows="5">${esc(memory.text || '')}</textarea></label>${memoryMetaFields(memory)}<div class="actions"><button class="primary" type="submit">${t('common.save')}</button><button class="secondary" type="button" onclick="closeModal()">${t('common.cancel')}</button></div></form></div>`; };
|
||||
window.saveMemoryEdit = (event,id) => { event.preventDefault(); const all=mems(), memory=all.find(item=>item.id===id); if(!memory)return; memory.text=document.querySelector('#memory-edit-note').value.trim(); memory.tags=tagList(document.querySelector('#memory-tags').value); memory.location=document.querySelector('#memory-place').value.trim(); memory.taskId=document.querySelector('#memory-task').value||''; memory.updatedAt=new Date().toISOString(); save('lifeos_memories',all); closeModal(); renderAll(); detail(id); };
|
||||
window.preserve = id => { const all = museum(), alreadyPreserved = all.some(item => item.memoryId === id); if (alreadyPreserved) { save('lifeos_museum', all.filter(item => item.memoryId !== id)); toast(t('timeline.unpreserved')); } else { save('lifeos_museum', [...all, { id: `artifact_${crypto.randomUUID()}`, memoryId: id, preservedAt: new Date().toISOString() }]); toast(t('timeline.added')); } closeModal(); renderAll(); };
|
||||
window.confirmDelete = id => { rootModal().innerHTML = `<div class="modal"><div class="modalbox"><div class="eyebrow">${t('timeline.confirmDelete')}</div><h2>${t('timeline.remove')}</h2><p class="sub">${t('timeline.irreversible')}</p><div class="actions"><button class="danger" onclick="deleteMemory('${id}')">${t('timeline.deleteMemory')}</button><button class="secondary" onclick="modal.innerHTML=''">${t('common.cancel')}</button></div></div></div>`; };
|
||||
window.deleteMemory = id => { save('lifeos_memories', mems().filter(item => item.id !== id)); save('lifeos_museum', museum().filter(item => item.memoryId !== id)); rootModal().innerHTML = ''; renderAll(); toast(t('timeline.removed')); };
|
||||
@@ -55,7 +69,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(), 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 = browserLanguage() === 'zh' ? t('system.detectedZh') : t('system.detectedEn');
|
||||
container.innerHTML = `<div class="eyebrow">${t('system.eyebrow')}</div><h1>${t('system.title')}</h1><div class="settings"><section><div class="eyebrow">${t('system.appearance')}</div><div class="choices">${['dark','light','auto'].map(item => `<button class="chip ${state.theme === item ? 'selected' : ''}" onclick="theme('${item}')">${t(`system.theme.${item}`)}</button>`).join('')}</div></section><section><div class="eyebrow">${t('system.language')}</div><p class="sub">${detected}</p><div class="choices"><button class="chip ${state.languageMode !== 'manual' ? 'selected' : ''}" onclick="setLanguage('auto')">${t('system.auto')}</button><button class="chip ${state.languageMode === 'manual' && state.language === 'en' ? 'selected' : ''}" onclick="setLanguage('en')">ENGLISH</button><button class="chip ${state.languageMode === 'manual' && state.language === 'zh' ? 'selected' : ''}" onclick="setLanguage('zh')">中文</button></div></section><section><div class="eyebrow">${t('system.data')}</div><div class="actions"><button class="primary" onclick="exportData()">${t('system.export')}</button><button class="secondary" onclick="document.querySelector('#importer').click()">${t('system.import')}</button><button class="danger" onclick="resetConfirm()">${t('system.reset')}</button><input id="importer" class="hidden" type="file" accept=".json" onchange="readImport(this)"></div></section><section><div class="eyebrow">${t('system.storage')}</div><p>${t('system.local')}<br>${lang() === 'zh' ? `${mems().length} 条记忆 · ${museum().length} 件博物馆藏品 · ${sys().tasksBoard?.tasks?.length || 0} 项待办 · 约 ${Math.ceil(bytes/1024)} KB` : `${mems().length} memories · ${museum().length} museum items · ${sys().tasksBoard?.tasks?.length || 0} tasks · Approx. ${Math.ceil(bytes/1024)} KB`}</p></section><section><div class="eyebrow">${t('system.dataStorage')}</div><p>${t('system.stored')}<br>${t('system.backups')}</p><p>${t('system.lastBackup',{value:state.lastBackup ? short(state.lastBackup.slice(0,10)) : t('common.never')})}${oldBackup ? `<br><b style="color:var(--accent)">${t('system.recommended')}</b>` : ''}</p></section><section><div class="eyebrow">${t('system.version')}</div><p>Life OS<br>Version 1.3.0 · Schema v1</p></section></div>`;
|
||||
container.innerHTML = `<div class="eyebrow">${t('system.eyebrow')}</div><h1>${t('system.title')}</h1><div class="settings"><section><div class="eyebrow">${t('system.appearance')}</div><div class="choices">${['dark','light','auto'].map(item => `<button class="chip ${state.theme === item ? 'selected' : ''}" onclick="theme('${item}')">${t(`system.theme.${item}`)}</button>`).join('')}</div></section><section><div class="eyebrow">${t('system.language')}</div><p class="sub">${detected}</p><div class="choices"><button class="chip ${state.languageMode !== 'manual' ? 'selected' : ''}" onclick="setLanguage('auto')">${t('system.auto')}</button><button class="chip ${state.languageMode === 'manual' && state.language === 'en' ? 'selected' : ''}" onclick="setLanguage('en')">ENGLISH</button><button class="chip ${state.languageMode === 'manual' && state.language === 'zh' ? 'selected' : ''}" onclick="setLanguage('zh')">中文</button></div></section><section><div class="eyebrow">${t('system.data')}</div><div class="actions"><button class="primary" onclick="exportData()">${t('system.export')}</button><button class="secondary" onclick="document.querySelector('#importer').click()">${t('system.import')}</button><button class="danger" onclick="resetConfirm()">${t('system.reset')}</button><input id="importer" class="hidden" type="file" accept=".json" onchange="readImport(this)"></div></section><section><div class="eyebrow">${t('system.storage')}</div><p>${t('system.local')}<br>${lang() === 'zh' ? `${mems().length} 条记忆 · ${museum().length} 件博物馆藏品 · ${sys().tasksBoard?.tasks?.length || 0} 项待办 · 约 ${Math.ceil(bytes/1024)} KB` : `${mems().length} memories · ${museum().length} museum items · ${sys().tasksBoard?.tasks?.length || 0} tasks · Approx. ${Math.ceil(bytes/1024)} KB`}</p></section><section><div class="eyebrow">${t('system.dataStorage')}</div><p>${t('system.stored')}<br>${t('system.backups')}</p><p>${t('system.lastBackup',{value:state.lastBackup ? short(state.lastBackup.slice(0,10)) : t('common.never')})}${oldBackup ? `<br><b style="color:var(--accent)">${t('system.recommended')}</b>` : ''}</p></section><section><div class="eyebrow">${t('system.version')}</div><p>Life OS<br>Version 1.4.0 · Schema v1</p></section></div>`;
|
||||
};
|
||||
window.importPreview = () => { const data = importData; rootModal().innerHTML = `<div class="modal"><div class="modalbox"><div class="eyebrow">${t('dialog.backup')}</div><h2>${t('dialog.ready')}</h2><p class="sub">${t('dialog.exported',{date:short(data.exportedAt.slice(0,10))})}<br>${t('dialog.memories',{count:data.memories.length})}<br>${t('dialog.museumItems',{count:data.museum.length})}</p><div class="actions"><button class="primary" onclick="applyImport('merge')">${t('dialog.merge')}</button><button class="danger" onclick="replaceConfirm()">${t('dialog.replaceAll')}</button><button class="secondary" onclick="modal.innerHTML=''">${t('common.cancel')}</button></div></div></div>`; };
|
||||
window.replaceConfirm = () => { rootModal().innerHTML = `<div class="modal"><div class="modalbox"><div class="eyebrow">${t('dialog.final')}</div><h2>${t('dialog.replaceQuestion')}</h2><p class="sub">${t('dialog.replaceWarning')}</p><div class="actions"><button class="danger" onclick="applyImport('replace')">${t('dialog.replace')}</button><button class="secondary" onclick="importPreview()">${t('common.cancel')}</button></div></div></div>`; };
|
||||
@@ -80,6 +94,9 @@
|
||||
window.resetConfirm = () => { rootModal().innerHTML = `<div class="modal"><div class="modalbox"><div class="eyebrow">${t('system.reset')}</div><h2>${t('dialog.resetQuestion')}</h2><p class="sub">${t('dialog.resetWarning')}</p><div class="actions"><button class="danger" onclick="['lifeos_memories','lifeos_museum','lifeos_profile','lifeos_settings','lifeos_system'].forEach(k=>localStorage.removeItem(k));location.reload()">${t('dialog.resetData')}</button><button class="secondary" onclick="modal.innerHTML=''">${t('common.cancel')}</button></div></div></div>`; };
|
||||
window.toast = key => { const message = key.includes('.') ? t(key) : key; const node = document.createElement('div'); node.className = 'toast'; node.textContent = message; document.body.append(node); setTimeout(() => node.remove(), 2000); };
|
||||
window.minWin = id => { const windowNode = windows[id]; if (!windowNode) return; windowNode.classList.add('min'); if (activeWindow === id) { activeWindow = Object.keys(windows).find(key => key !== id && !windows[key].classList.contains('min')) || null; if (activeWindow) focus(windows[activeWindow]); } renderTaskbar(); toast('feedback.minimized'); };
|
||||
const memoryStyle = document.createElement('style');
|
||||
memoryStyle.textContent = `.memory-meta-form{display:grid;gap:9px;margin-top:12px}.memory-meta-form label,.memory-edit-form>label{display:grid;gap:6px;color:var(--muted);font-size:12px}.memory-meta-form input,.memory-meta-form select,.memory-edit-form textarea{width:100%;min-width:0;border:1px solid var(--line);background:var(--panel2);border-radius:8px;padding:10px 11px;color:var(--ink);outline:none;font:inherit}.memory-meta-form input:focus,.memory-meta-form select:focus,.memory-edit-form textarea:focus,.timeline-tools input:focus,.timeline-tools select:focus{border-color:var(--accent)}.memory-edit-form{display:grid;gap:12px;max-height:min(80vh,720px);overflow:auto}.memory-edit-form h2{font-size:21px;margin:0}.memory-edit-form textarea{resize:vertical;line-height:1.55}.memory-edit-form .memory-meta-form{margin:0}.memory-edit-form .actions{margin:0}.timeline-tools{display:grid;grid-template-columns:minmax(0,1fr) 190px;gap:8px;margin:17px 0 11px}.timeline-tools input,.timeline-tools select{width:100%;min-width:0;height:40px;border:1px solid var(--line);border-radius:8px;background:var(--panel2);color:var(--ink);padding:0 11px;outline:none;font:13px Manrope,system-ui}.timeline-tools input::placeholder{color:var(--muted)}.memory-open{display:block!important;text-align:left}.memory-badges{display:flex;gap:6px;flex-wrap:wrap;margin-top:9px}.memory-badges span,.memory-detail-meta{color:var(--muted);font-size:12px}.memory-badges span{border:1px solid var(--line);background:var(--panel2);border-radius:99px;padding:4px 8px}.detail-badges{margin:0 0 14px}.text-link{border:0;background:none;padding:0;color:var(--accent);text-decoration:underline}.memory-detail-meta{margin:8px 0}.memory-edit-form .sub{margin:0}@media(max-width:600px){.timeline-tools{grid-template-columns:1fr 130px}.memory-badges span{font-size:11px}.memory-meta-form label,.memory-edit-form>label{font-size:13px}}`;
|
||||
document.head.append(memoryStyle);
|
||||
// Render every surface again after the overrides are installed; no DOM walking is involved.
|
||||
renderIcons(); renderAll(); applyLanguage();
|
||||
})();
|
||||
+4
-2
@@ -5,7 +5,8 @@
|
||||
common: { save: 'SAVE', cancel: 'CANCEL', edit: 'EDIT', delete: 'DELETE', close: 'Close', open: 'Open', minimize: 'Minimize', maximize: 'Maximize', restore: 'Restore window', energy: 'ENERGY', days: 'DAYS', never: 'NEVER' },
|
||||
today: { prompt: 'How was your day?', placeholder: 'Write something about today...', archive: 'ARCHIVE TODAY', update: 'UPDATE MEMORY', weather: "TODAY'S WEATHER", energy: 'ENERGY LEVEL', film: "TODAY'S FILM", archiving: 'ARCHIVING MEMORY...', archived: 'MEMORY ARCHIVED' },
|
||||
weather: { clear: 'Clear', light: 'Light', cloudy: 'Cloudy', rain: 'Rain', storm: 'Storm' },
|
||||
timeline: { eyebrow: 'MEMORY ARCHIVE', emptyTitle: 'No memories yet.', emptyBody: 'Open Today when you are ready to keep one.', detail: 'MEMORY DETAIL', noNote: 'No written note.', editing: 'EDITING AS TODAY — SAVE TO UPDATE', preserved: 'PRESERVED', addMuseum: 'ADD TO MUSEUM', confirmDelete: 'CONFIRM DELETE', remove: 'Remove this memory?', irreversible: 'This cannot be undone.', deleteMemory: 'DELETE MEMORY', removed: 'MEMORY REMOVED', added: 'MEMORY PRESERVED', unpreserved: 'REMOVED FROM MUSEUM' },
|
||||
timeline: { eyebrow: 'MEMORY ARCHIVE', heading: '{count} OF {total} MEMORIES', search: 'Search memories', searchPlaceholder: 'Search notes, tags, places, tasks…', filterTag: 'Filter by tag', allTags: 'ALL TAGS', noMatches: 'No memories match these filters.', favorite: 'FAVORITE', emptyTitle: 'No memories yet.', emptyBody: 'Open Today when you are ready to keep one.', detail: 'MEMORY DETAIL', noNote: 'No written note.', editing: 'EDITING AS TODAY — SAVE TO UPDATE', preserved: 'FAVORITED', addMuseum: 'ADD TO MUSEUM', confirmDelete: 'CONFIRM DELETE', remove: 'Remove this memory?', irreversible: 'This cannot be undone.', deleteMemory: 'DELETE MEMORY', removed: 'MEMORY REMOVED', added: 'MEMORY FAVORITED', unpreserved: 'REMOVED FROM FAVORITES' },
|
||||
memory: { tags: 'TAGS', tagsPlaceholder: 'Separate tags with commas', place: 'PLACE / SCENE (OPTIONAL)', placePlaceholder: 'Where or what was the setting?', linkTask: 'RELATED TASK (OPTIONAL)', noTask: 'NO RELATED TASK', relatedTask: 'Related task', favorite: 'ADD TO FAVORITES', edit: 'EDIT MEMORY DETAILS', note: 'NOTE' },
|
||||
museum: { eyebrow: 'MUSEUM', title: 'Preserved moments', subtitle: 'Worth keeping for the long view.', empty: 'A museum begins with one preserved moment.', preserved: 'STATUS: PRESERVED' },
|
||||
insights: { eyebrow: 'LAST 30 DAYS', title: 'Insights, quietly.', weather: 'WEATHER', average: 'AVERAGE ENERGY', active: 'MOST ACTIVE DAY', current: 'CURRENT STREAK', longest: 'Longest streak: {count} days', noEntries: 'No entries yet' },
|
||||
profile: { eyebrow: 'SYSTEM PROFILE', online: 'LIFE OS ONLINE', daysOnline: 'DAYS ONLINE', memories: 'MEMORIES', museumItems: 'MUSEUM ITEMS', streak: 'CURRENT STREAK', longest: 'LONGEST STREAK', commonWeather: 'COMMON WEATHER', personal: 'PERSONAL SYSTEM', name: 'Display Name', birthday: 'Birthday (optional)', start: 'Life OS Start Date', motto: 'Personal Motto', save: 'SAVE PROFILE', saved: 'PROFILE SAVED', average: 'AVERAGE ENERGY {value} / 5' },
|
||||
@@ -21,7 +22,8 @@
|
||||
common: { save: '保存', cancel: '取消', edit: '编辑', delete: '删除', close: '关闭', open: '打开', minimize: '最小化', maximize: '最大化', restore: '恢复窗口', energy: '能量', days: '天', never: '从未' },
|
||||
today: { prompt: '今天过得怎么样?', placeholder: '写下今天的片段…', archive: '归档今日', update: '更新记忆', weather: '今日天气', energy: '能量等级', film: '今日电影', archiving: '正在归档…', archived: '记忆已归档' },
|
||||
weather: { clear: '晴朗', light: '微光', cloudy: '多云', rain: '雨天', storm: '风暴' },
|
||||
timeline: { eyebrow: '记忆档案', emptyTitle: '还没有记忆。', emptyBody: '准备好记录时,打开“今日”。', detail: '记忆详情', noNote: '尚未写下记录。', editing: '正在以今日记录编辑 — 保存即可更新', preserved: '已珍藏', addMuseum: '加入博物馆', confirmDelete: '确认删除', remove: '删除这条记忆?', irreversible: '此操作无法撤销。', deleteMemory: '删除记忆', removed: '记忆已删除', added: '记忆已珍藏', unpreserved: '已取消珍藏' },
|
||||
timeline: { eyebrow: '记忆档案', heading: '{count} / {total} 条记忆', search: '搜索记忆', searchPlaceholder: '搜索记录、标签、地点或待办…', filterTag: '按标签筛选', allTags: '全部标签', noMatches: '没有符合筛选条件的记忆。', favorite: '已收藏', emptyTitle: '还没有记忆。', emptyBody: '准备好记录时,打开“今日”。', detail: '记忆详情', noNote: '尚未写下记录。', editing: '正在以今日记录编辑 — 保存即可更新', preserved: '已收藏', addMuseum: '加入收藏', confirmDelete: '确认删除', remove: '删除这条记忆?', irreversible: '此操作无法撤销。', deleteMemory: '删除记忆', removed: '记忆已删除', added: '记忆已收藏', unpreserved: '已取消收藏' },
|
||||
memory: { tags: '标签', tagsPlaceholder: '多个标签请用逗号分隔', place: '地点 / 场景(选填)', placePlaceholder: '在哪里,或当时是什么场景?', linkTask: '关联待办(选填)', noTask: '不关联待办', relatedTask: '关联待办', favorite: '加入收藏', edit: '编辑记忆详情', note: '记录内容' },
|
||||
museum: { eyebrow: '人生博物馆', title: '被珍藏的瞬间', subtitle: '值得留给漫长回望的片段。', empty: '从一份被珍藏的记忆开始。', preserved: '状态:已珍藏' },
|
||||
insights: { eyebrow: '最近 30 天', title: '安静地看见时间。', weather: '天气', average: '平均能量', active: '最活跃日', current: '当前连续记录', longest: '最长连续记录:{count} 天', noEntries: '暂无记录' },
|
||||
profile: { eyebrow: '系统档案', online: 'LIFE OS 在线', daysOnline: '系统运行天数', memories: '记忆', museumItems: '博物馆藏品', streak: '当前连续记录', longest: '最长连续记录', commonWeather: '常见天气', personal: '个人系统', name: '显示名称', birthday: '生日(选填)', start: 'Life OS 启动日期', motto: '个人格言', save: '保存资料', saved: '资料已保存', average: '平均能量 {value} / 5' },
|
||||
|
||||
Loaded 3 of 7 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user