diff --git a/.gitignore b/.gitignore index 335ec95..f43c4cd 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ *.tar.gz +dist/ diff --git a/.openai/hosting.json b/.openai/hosting.json index cf4e28b..8d1ff98 100644 --- a/.openai/hosting.json +++ b/.openai/hosting.json @@ -1,6 +1,3 @@ { - "static": { - "directory": "dist" - }, "project_id": "appgprj_6ab265f0f7dc81919b4e281000e6f7a9" } diff --git a/README.md b/README.md index 538066f..5b607f4 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ - **Museum**:将重要记忆长期珍藏为馆藏。 - **Insights / Profile**:查看情绪、能量、连续记录与个人系统状态。 - **System**:深浅主题、自动识别浏览器语言(也可固定中文/English)、JSON 导入导出与本地存储说明。 -- **Bing 每日一图**:桌面壁纸每日自动更新;接口不可用时保留内置背景。 +- **Bing 每日一图**:桌面壁纸每日自动更新;由 Life OS 同源代理读取,避免浏览器跨域限制。 - **Windows 式操作**:桌面端单击选择、双击打开;右键桌面或 App 图标可使用快捷菜单。 - **快捷键与草稿恢复**:`Ctrl / ⌘ + K` 打开启动器,`Ctrl / ⌘ + F` 搜索记忆,`Ctrl / ⌘ + S` 归档 Today;当天未归档文字会自动恢复。 - **移动端**:App 以全屏方式打开,保留任务栏恢复入口。 @@ -33,19 +33,25 @@ ## 运行 -这是一个无构建步骤的静态站点。用任意静态文件服务器指向 `dist/` 即可,例如: +这是一个轻量的同源 Worker 网站。Worker 会代理 Bing 每日图片,避免浏览器跨域限制导致壁纸回退为纯色。 + +安装依赖后构建并运行验证: ```bash -npx serve dist +npm run build +npm run validate ``` -直接打开 `dist/index.html` 也能查看基础界面;联网时 Bing 每日壁纸会自动加载。 +构建产物位于 `dist/`,其中包含 Cloudflare Worker 入口与界面资源。 ## 项目结构 ```text -dist/index.html # 整个应用:界面、样式与本地数据逻辑 -.openai/hosting.json # Sites 静态托管配置 +public/index.html # 界面、样式与本地数据逻辑 +public/addons.js # 窗口与辅助交互 +worker/index.js # 同源 Bing 每日壁纸代理 +scripts/build.mjs # 生成 Worker 部署产物 +.openai/hosting.json # Sites 托管配置 ``` ## 隐私说明 diff --git a/package.json b/package.json new file mode 100644 index 0000000..5b864e5 --- /dev/null +++ b/package.json @@ -0,0 +1,9 @@ +{ + "name": "life-os", + "private": true, + "type": "module", + "scripts": { + "build": "node scripts/build.mjs", + "validate": "node scripts/validate.mjs" + } +} diff --git a/dist/addons.js b/public/addons.js similarity index 100% rename from dist/addons.js rename to public/addons.js diff --git a/dist/index.html b/public/index.html similarity index 97% rename from dist/index.html rename to public/index.html index 0111c8f..dd3bb7a 100644 --- a/dist/index.html +++ b/public/index.html @@ -12,7 +12,7 @@ function load(k,d){try{return JSON.parse(localStorage.getItem(k))||d}catch{retur const ZH={'LIFE OS / PERSONAL DESKTOP':'LIFE OS / 人生操作系统','TODAY':'今日','TIMELINE':'时间线','MUSEUM':'人生博物馆','INSIGHTS':'洞察','PROFILE':'个人资料','SYSTEM':'系统','SYSTEM ONLINE · LIFE OS v0.1':'系统在线 · LIFE OS v0.1','SYSTEM DATE':'系统日期','MEMORY ARCHIVE':'记忆档案','MEMORIES ARCHIVED':'条记忆已归档','MEMORY ARCHIVED':'记忆已归档','How was your day?':'今天过得怎么样?','Write something about today...':'写下今天的片段…',"TODAY'S WEATHER":'今日天气','ENERGY LEVEL':'能量等级',"TODAY'S FILM":'今日电影','ARCHIVE TODAY':'归档今日','UPDATE MEMORY':'更新记忆','ARCHIVING MEMORY...':'正在归档…','MUSEUM':'人生博物馆','Preserved moments':'被珍藏的瞬间','Worth keeping for the long view.':'值得留给漫长回望的片段。','A museum begins with one preserved moment.':'从一份被珍藏的记忆开始。','STATUS: PRESERVED':'状态:已珍藏','LAST 30 DAYS':'最近 30 天','Insights, quietly.':'安静地看见时间。','WEATHER':'天气','AVERAGE ENERGY':'平均能量','MOST ACTIVE DAY':'最活跃日','CURRENT STREAK':'当前连续记录','LONGEST STREAK':'最长连续记录','SYSTEM PROFILE':'系统档案','LIFE OS ONLINE':'LIFE OS 在线','DAYS ONLINE':'系统运行天数','MEMORIES':'记忆','MUSEUM ITEMS':'博物馆藏品','COMMON WEATHER':'常见天气','PERSONAL SYSTEM':'个人系统','Display Name':'显示名称','Birthday (optional)':'生日(选填)','Life OS Start Date':'Life OS 启动日期','Personal Motto':'个人格言','SAVE PROFILE':'保存资料','PROFILE SAVED':'资料已保存','SYSTEM SETTINGS':'系统设置','System, local and yours.':'你的本地个人系统。','APPEARANCE':'外观','DATA':'数据','STORAGE':'存储','DATA STORAGE':'数据存储','Your data is stored locally in this browser.':'你的数据仅存储在此浏览器中。','Create regular backups to prevent data loss.':'定期备份,让记忆一直在。','LAST BACKUP · NEVER':'上次备份 · 从未','BACKUP RECOMMENDED':'建议备份','VERSION':'版本','EXPORT DATA':'导出数据','IMPORT DATA':'导入数据','RESET LIFE OS':'重置 Life OS','LANGUAGE':'语言','ENGLISH':'English','CHINESE':'中文','Dark':'深色','Light':'浅色','Auto':'自动','No memories yet.':'还没有记忆。','Open Today when you are ready to keep one.':'准备好记录时,打开“今日”。','MEMORY DETAIL':'记忆详情','EDIT':'编辑','ADD TO MUSEUM':'加入博物馆','PRESERVED':'已珍藏','DELETE':'删除','CONFIRM DELETE':'确认删除','Remove this memory?':'删除这条记忆?','This cannot be undone.':'此操作无法撤销。','DELETE MEMORY':'删除记忆','CANCEL':'取消','BACKUP CREATED':'备份已创建','INVALID BACKUP FILE':'无效备份文件','SYSTEM RESTORED':'系统已恢复','RESTORING SYSTEM...':'正在恢复系统…','MINIMIZED — TAP THE APP ICON TO RESTORE':'已最小化 — 点击任务栏图标即可恢复'}; function browserLanguage(){let ls=navigator.languages?.length?navigator.languages:[navigator.language||'en'];return ls.some(x=>String(x).toLowerCase().startsWith('zh'))?'zh':'en'}function lang(){let s=settings();return s.languageMode==='manual'&&['en','zh'].includes(s.language)?s.language:browserLanguage()}function applyLanguage(){let zh=lang()==='zh';document.title=zh?'Life OS / 人生操作系统':'Life OS';let map=zh?ZH:Object.fromEntries(Object.entries(ZH).map(([a,b])=>[b,a]));let walker=document.createTreeWalker(document.body,NodeFilter.SHOW_TEXT);let n=[];while(walker.nextNode())n.push(walker.currentNode);n.forEach(x=>{let v=x.nodeValue.trim();if(map[v])x.nodeValue=x.nodeValue.replace(v,map[v])});document.querySelectorAll('[placeholder]').forEach(x=>{if(zh&&ZH[x.placeholder])x.placeholder=ZH[x.placeholder];if(!zh){let k=Object.entries(ZH).find(([,v])=>v===x.placeholder)?.[0];if(k)x.placeholder=k}})}function setLanguage(v){let s=settings();s.language=v;s.languageMode=v==='auto'?'auto':'manual';save('lifeos_settings',s);renderAll();applyLanguage();toast(v==='auto'?(lang()==='zh'?'已跟随浏览器语言:中文':'Following browser language: English'):v==='zh'?'语言已切换为中文':'Language set to English')} function day(){return new Date().toISOString().slice(0,10)}function fmtDate(d){return new Date(d+'T12:00:00').toLocaleDateString('en-US',{weekday:'long',month:'long',day:'numeric',year:'numeric'})}function shortDate(d){return new Date(d+'T12:00:00').toLocaleDateString('en-US',{month:'short',day:'numeric'}).toUpperCase()}function cap(x){return x[0].toUpperCase()+x.slice(1)}function esc(s){return (s||'').replace(/[&<>]/g,x=>({'&':'&','<':'<','>':'>'}[x]))} -function applyWallpaper(w){if(!w?.url)return;desktop.style.backgroundImage=`linear-gradient(120deg,color-mix(in srgb,var(--bg) 68%,transparent),color-mix(in srgb,var(--bg) 36%,transparent)),url('${w.url}')`;desktop.style.backgroundSize='cover';desktop.style.backgroundPosition='center';desktop.title=`Bing daily image: ${w.title||'Daily image'}`}async function loadBingWallpaper(){let s=settings();if(s.wallpaper?.date===day()){applyWallpaper(s.wallpaper);return}try{let r=await fetch('https://www.bing.com/HPImageArchive.aspx?format=js&idx=0&n=1&mkt=en-US'),d=(await r.json()).images?.[0];if(!d)throw Error('No Bing image');s.wallpaper={date:day(),url:'https://www.bing.com'+d.url,title:d.copyright||'Bing daily image'};save('lifeos_settings',s);applyWallpaper(s.wallpaper)}catch(e){console.info('Bing wallpaper unavailable; using the calm fallback.',e)}}function init(){let s=settings();document.documentElement.dataset.theme=s.theme==='auto'?(matchMedia('(prefers-color-scheme:light)').matches?'light':'dark'):s.theme;renderIcons();renderTaskbar();loadBingWallpaper();setInterval(clock,1000);clock();setTimeout(applyLanguage,0);setTimeout(()=>boot.remove(),650)} +function applyWallpaper(w){if(!w?.url)return;desktop.style.backgroundImage=`linear-gradient(120deg,color-mix(in srgb,var(--bg) 68%,transparent),color-mix(in srgb,var(--bg) 36%,transparent)),url('${w.url}')`;desktop.style.backgroundSize='cover';desktop.style.backgroundPosition='center';desktop.title=`Bing daily image: ${w.title||'Daily image'}`}async function loadBingWallpaper(){let s=settings();if(s.wallpaper?.date===day()&&s.wallpaper?.source==='lifeos-proxy'){applyWallpaper(s.wallpaper);return}try{let r=await fetch('/api/bing-wallpaper'),d=await r.json();if(!r.ok||!d.url)throw Error(d.error||'No Bing image');s.wallpaper={date:day(),url:'/api/bing-wallpaper-image?date='+day(),title:d.title||'Bing daily image',source:'lifeos-proxy'};save('lifeos_settings',s);applyWallpaper(s.wallpaper)}catch(e){console.info('Bing wallpaper unavailable; using the calm fallback.',e)}}function init(){let s=settings();document.documentElement.dataset.theme=s.theme==='auto'?(matchMedia('(prefers-color-scheme:light)').matches?'light':'dark'):s.theme;renderIcons();renderTaskbar();loadBingWallpaper();setInterval(clock,1000);clock();setTimeout(applyLanguage,0);setTimeout(()=>boot.remove(),650)} function desktopIconClick(id,e){if(innerWidth<650){openApp(id);return}selectedIcon=id;document.querySelectorAll('.app-icon').forEach(x=>x.querySelector('.glyph').style.outline='none');e.currentTarget.querySelector('.glyph').style.outline='2px solid var(--accent)'}function renderIcons(){icons.innerHTML=Object.entries(APPS).map(([id,[g,n]])=>``).join('')}function renderTaskbar(){let ids=innerWidth<650?Object.keys(windows):['today','timeline','museum','insights'];tasks.innerHTML=ids.map(id=>``).join('')} function clock(){let d=new Date();document.querySelector('#clock').textContent=d.toLocaleTimeString([], {hour:'2-digit',minute:'2-digit'});document.querySelector('#date').textContent=d.toLocaleDateString('en-US',{month:'short',day:'numeric'}).toUpperCase()}function toggleLauncher(){launcher.classList.toggle('hidden');calendar.classList.add('hidden');launcher.innerHTML=`
${mems().length} memories · ${museum().length} preserved
`}function hideContext(){document.querySelector('#life-context')?.remove()}function showContext(e,id){e.preventDefault();e.stopPropagation();hideContext();let zh=lang()==='zh',labels=zh?{open:'打开',min:'最小化',close:'关闭',refresh:'刷新桌面',new:'新建今日记录',system:'系统设置'}:{open:'Open',min:'Minimize',close:'Close',refresh:'Refresh desktop',new:'New today entry',system:'System settings'},actions=id?`${windows[id]?``:''}`:`