'use strict'; pageInfo.journal=['个人记事','一言一照,都是阅历。私藏或分享,由自己决定。']; let journalGeneration=0,journalData=null,journalPage=0,journalUpload=false,journalRequest=null,journalRecorder=null,journalTracks=null,journalVoice=[],journalVoiceTimer=null,journalConfigRevision=0; const journalImages=new Map();let journalImageBytes=0; const journalId=()=>[...crypto.getRandomValues(new Uint8Array(16))].map(b=>b.toString(16).padStart(2,'0')).join(''); const journalToday=()=>{const d=new Date();return [d.getFullYear(),String(d.getMonth()+1).padStart(2,'0'),String(d.getDate()).padStart(2,'0')].join('-');}; function journalBusy(value){journalUpload=value;$('#journal-save').disabled=value;$('#journal-new').disabled=value;for(const control of $('#journal-filter').elements)control.disabled=value;$('#journal-prev').disabled=value||!journalData?.page;$('#journal-next').disabled=value||!journalData||(journalData.page+1)*20>=journalData.total;$('#journal-record').disabled=value||!journalData?.connection.connected||!window.isSecureContext||!navigator.mediaDevices?.getUserMedia||!window.MediaRecorder;} function journalMessage(text){$('#journal-status').textContent=text;} function journalValid(g){return g===journalGeneration&&loggedIn&&activePage==='journal';} function journalMediaUrl(id,download=false){return '/media/journal?'+new URLSearchParams({id,family:state.familyId,...(download?{download:'1'}:{})});} function stopJournalVoice(discard=true){ if(discard)journalVoice=[];if(journalRecorder&&journalRecorder.state!=='inactive')journalRecorder.stop(); journalTracks?.getTracks().forEach(t=>t.stop());journalTracks=null;clearTimeout(journalVoiceTimer);journalVoiceTimer=null; $('#journal-record').disabled=false;$('#journal-stop-record').hidden=true; } function leaveJournal(){ journalGeneration++;journalRequest?.abort();journalRequest=null;journalBusy(false);stopJournalVoice(); $$('#journal-list video,#journal-list audio').forEach(v=>{v.pause();v.removeAttribute('src');v.load();}); for(const image of journalImages.values())URL.revokeObjectURL(image.url);journalImages.clear();journalImageBytes=0; $('#journal-list').replaceChildren();$('#journal-files').disabled=false;$('#journal-save').disabled=false; } function clearJournalForm(){const f=$('#journal-form');f.reset();for(const key of ['id','revision','clientId'])f.elements[key].value='';} function resetJournal(){leaveJournal();journalData=null;journalPage=0;clearJournalForm();$('#journal-filter').reset();$('#journal-upload-list').replaceChildren();$('#journal-status').textContent='';$('#journal-person').replaceChildren();$('#journal-files').disabled=true;$('#journal-record').disabled=true;$('#baidu-config').reset();$('#baidu-status').textContent='';$('#baidu-config-status').textContent='';} function newJournal(){clearJournalForm();const f=$('#journal-form');for(const input of $('#journal-upload-list').querySelectorAll('input[type=checkbox]'))input.checked=false;f.elements.clientId.value=journalId();f.elements.occurredOn.value=journalToday();$('#journal-save').textContent='保存记事';journalMessage('默认仅自己可见。选择“当前家庭/场所”后才会进入动态。');renderJournalUploads();} async function loadJournal(){ const g=++journalGeneration;if(!loggedIn)return; if(!$('#journal-form').elements.clientId.value)newJournal(); $('#journal-person-label').hidden=$('#journal-mode').value!=='person'; const query=new URLSearchParams({mode:$('#journal-mode').value,person:$('#journal-person').value,category:$('#journal-category').value,search:$('#journal-search').value,from:$('#journal-from').value,to:$('#journal-to').value,page:String(journalPage)}); $('#journal-query-status').textContent='正在读取记事…'; try{ const result=await api('/api/journal?'+query);if(!journalValid(g))return;journalData=result; const selected=$('#journal-person').value;$('#journal-person').innerHTML=''+Object.entries(result.people).map(([id,name])=>``).join('');$('#journal-person').value=selected; $('#journal-query-status').textContent=result.total?`共 ${result.total} 条 · 第 ${result.page+1} 页`:'暂时没有记事。写下今天,也可以补记从前。'; $('#journal-prev').disabled=!result.page;$('#journal-next').disabled=(result.page+1)*20>=result.total; $('#journal-cloud-note').textContent=result.connection.connected&&!result.connection.reconnect?'附件保存到后台统一连接的百度网盘;可见范围由系统控制。':'文字可直接保存。照片、视频、语音需由超级管理员先连接百度网盘。'; $('#journal-files').disabled=journalUpload||!result.connection.connected||result.connection.reconnect; $('#journal-record').disabled=$('#journal-files').disabled||!window.isSecureContext||!navigator.mediaDevices?.getUserMedia||!window.MediaRecorder; renderJournalUploads();renderJournalEntries(); }catch(e){if(!e.stale&&journalValid(g))$('#journal-query-status').textContent=e.message;} } function renderJournalUploads(){ const selected=new Set([...$('#journal-upload-list').querySelectorAll('input:checked')].map(e=>e.value)); const editing=journalData?.items.find(e=>e.id===$('#journal-form').elements.id.value); const available=[...(journalData?.uploads||[])];for(const media of editing?.media||[])if(!available.some(m=>m.id===media.id))available.push(media); $('#journal-upload-list').innerHTML=available.map(m=>`
${m.status==='receiving'?``:''}
`).join(''); } function renderJournalEntries(){ for(const value of journalImages.values())URL.revokeObjectURL(value.url);journalImages.clear();journalImageBytes=0; $('#journal-list').innerHTML=journalData.items.map(e=>`
${esc(e.occurredOn)} · ${esc(e.category)} · ${e.visibility==='private'?'仅自己可见':'当前家庭/场所'}
${e.canEdit?`
`:''}

${esc(e.text)}

${e.media.map(m=>m.mime.startsWith('image/')?`
`:`
<${m.mime.startsWith('audio/')?'audio':'video'} controls preload="none" playsinline src="${esc(journalMediaUrl(m.id))}">
${esc(m.name)} 下载原文件
点击播放后缓冲;编码不兼容时可下载原文件。
`).join('')}
评论 ${e.commentCount}
${e.comments.map(c=>journalComment(e,c)).join('')}
`).join(''); } function journalComment(entry,c){return `

${esc(c.authorName)} ${esc(c.text)} ${esc(new Date(c.createdAt).toLocaleString())}${c.canDelete?` `:''}

`;} $('#journal-form').addEventListener('submit',async event=>{ event.preventDefault();if(journalUpload){journalMessage('请等当前附件上传完成,或取消上传后保存文字。');return;} const f=event.currentTarget,g=journalGeneration,button=$('#journal-save');button.disabled=true; try{await api('/api/journal',{...formValues(f),revision:Number(f.elements.revision.value||0),mediaIds:[...$('#journal-upload-list').querySelectorAll('input:checked')].map(e=>e.value)});if(!journalValid(g))return;newJournal();$('#journal-mode').value='mine';journalPage=0;await loadJournal();journalMessage('已保存,动态与个人阅历使用同一条记录。');} catch(e){if(!e.stale&&journalValid(g))journalMessage(e.message);}finally{if(journalValid(g)||activePage==='journal')button.disabled=false;} }); $('#journal-new').onclick=newJournal; $('#journal-filter').onsubmit=e=>{e.preventDefault();journalPage=0;loadJournal();}; $('#journal-mode').onchange=()=>{$('#journal-person-label').hidden=$('#journal-mode').value!=='person';journalPage=0;loadJournal();}; $('#journal-prev').onclick=()=>{journalPage--;loadJournal();};$('#journal-next').onclick=()=>{journalPage++;loadJournal();}; async function uploadJournalFile(file,resume=''){ if(!file||journalUpload)return;if(!journalData?.connection.connected){journalMessage('请先由超级管理员连接百度网盘。');return;}if(file.size>268435456||!file.size){journalMessage('单个文件须为 1 字节至 256 MB。');return;} journalBusy(true);const g=journalGeneration;$('#journal-save').disabled=true;$('#journal-files').disabled=true;$('#journal-cancel-upload').hidden=false; try{ const hashes=await ZhaoFileHash.blocks(file,(n,total)=>journalMessage('正在计算文件校验:'+Math.round(n/total*100)+'%'),()=>journalValid(g)&&journalUpload); if(!journalValid(g)||!journalUpload)return; let task=await api('/api/journal/upload',{id:resume,name:file.name,size:file.size,mime:file.type,hashes}); if(!journalValid(g)||!journalUpload)return; for(let offset=task.received;offset{uploadJournalFile(e.target.files[0]);e.target.value='';}; $('#journal-cancel-upload').onclick=()=>{journalBusy(false);journalRequest?.abort();journalMessage('已暂停。重新选择原文件可继续上传。');}; $('#journal-record').onclick=async()=>{ if(journalUpload||!journalData?.connection.connected)return; const g=journalGeneration;try{ const tracks=await navigator.mediaDevices.getUserMedia({audio:true});if(!journalValid(g)){tracks.getTracks().forEach(t=>t.stop());return;} journalTracks=tracks;journalVoice=[];const type=['audio/webm;codecs=opus','audio/mp4','audio/ogg;codecs=opus'].find(t=>MediaRecorder.isTypeSupported(t)); if(!type){stopJournalVoice();throw Error('当前浏览器不能直接录音,可上传已有音频文件。');} const recorder=new MediaRecorder(tracks,{mimeType:type});journalRecorder=recorder; recorder.ondataavailable=e=>{if(journalValid(g)&&e.data.size)journalVoice.push(e.data);}; recorder.onstop=()=>{if(journalValid(g)&&journalVoice.length){const blob=new Blob(journalVoice,{type}),extension=type.includes('mp4')?'m4a':type.includes('ogg')?'ogg':'webm';uploadJournalFile(new File([blob],'语音-'+Date.now()+'.'+extension,{type}));}journalVoice=[];}; recorder.start();$('#journal-record').disabled=true;$('#journal-stop-record').hidden=false;journalMessage('正在录音,最长 5 分钟。点击结束后上传。');journalVoiceTimer=setTimeout(()=>stopJournalVoice(false),300000); }catch(e){if(journalValid(g))journalMessage(e.name==='NotAllowedError'?'未获得麦克风权限,可上传已有语音文件。':e.message);} }; $('#journal-stop-record').onclick=()=>stopJournalVoice(false); document.addEventListener('change',e=>{if(e.target.dataset.journalResume){uploadJournalFile(e.target.files[0],e.target.dataset.journalResume);e.target.value='';}}); document.addEventListener('click',async event=>{ const b=event.target.closest('button');if(!b)return;const g=journalGeneration; if(b.dataset.journalPerson){$('#journal-mode').value='person';$('#journal-person-label').hidden=false;$('#journal-person').value=b.dataset.journalPerson;journalPage=0;loadJournal();return;} if(b.dataset.journalEdit){const e=journalData.items.find(e=>e.id===b.dataset.journalEdit);if(!e)return;const f=$('#journal-form');clearJournalForm();for(const key of ['id','revision','clientId','text','occurredOn','category','visibility'])f.elements[key].value=e[key];renderJournalUploads();$('#journal-save').textContent='保存修改';for(const input of $('#journal-upload-list').querySelectorAll('input[type=checkbox]'))input.checked=e.mediaIds.includes(input.value);f.scrollIntoView({behavior:'smooth'});return;} if(b.dataset.loadPhoto){ b.disabled=true;try{ const id=b.dataset.loadPhoto;let cached=journalImages.get(id); if(!cached){if(Number(b.dataset.size)>20*1024*1024||journalImageBytes+Number(b.dataset.size)>20*1024*1024)throw Error('照片较大,请下载原图查看。'); journalImageBytes+=Number(b.dataset.size);b.textContent='照片缓冲中…';const response=await fetch(journalMediaUrl(id));if(!response.ok)throw Error('照片读取失败,请稍后重试');const blob=await response.blob();if(!journalValid(g))return; cached={url:URL.createObjectURL(blob),size:blob.size};journalImages.set(id,cached);} const img=b.parentElement.querySelector('img');img.src=cached.url;img.hidden=false;b.hidden=true; }catch(e){if(journalValid(g)){if(b.textContent==='照片缓冲中…')journalImageBytes=Math.max(0,journalImageBytes-Number(b.dataset.size));b.textContent=e.message;b.disabled=false;const link=document.createElement('a');link.href=journalMediaUrl(b.dataset.loadPhoto,true);link.textContent='下载照片';b.after(link);}}return; } const id=b.dataset.journalLike||b.dataset.journalDelete||b.dataset.journalEntry; if(!id&&!b.dataset.removeUpload)return;b.disabled=true; try{ if(b.dataset.removeUpload){await api('/api/journal/remove-upload',{id:b.dataset.removeUpload});if(journalValid(g))await loadJournal();return;} const entry=journalData.items.find(e=>e.id===id);if(!entry)return; if(b.dataset.journalLike){await api('/api/journal/action',{id,action:'like',liked:!entry.liked});if(!journalValid(g))return;entry.liked=!entry.liked;entry.likes+=entry.liked?1:-1;b.textContent=(entry.liked?'♥ 已赞':'♡ 点赞')+' · '+entry.likes;b.setAttribute('aria-pressed',String(entry.liked));} if(b.dataset.journalDelete){if(!confirm('删除这条记事?网盘原文件保留。'))return;await api('/api/journal/action',{id,action:'delete',revision:entry.revision});if(journalValid(g))loadJournal();} if(b.dataset.deleteComment){await api('/api/journal/action',{id,action:'delete-comment',commentId:b.dataset.deleteComment});if(journalValid(g)){b.closest('.journal-comment').remove();entry.commentCount--;document.querySelector(`[data-entry="${id}"] [data-comment-count]`).textContent=entry.commentCount;}} }catch(e){if(!e.stale&&journalValid(g))journalMessage(e.message);}finally{b.disabled=false;} }); document.addEventListener('submit',async event=>{ const f=event.target;if(!f.dataset.journalComment)return;event.preventDefault();const id=f.dataset.journalComment,g=journalGeneration,b=f.querySelector('button');b.disabled=true;const text=f.elements.text.value; f.dataset.clientId ||= journalId(); try{await api('/api/journal/action',{id,action:'comment',text,clientId:f.dataset.clientId});if(!journalValid(g))return; const entry=journalData.items.find(e=>e.id===id),comment={id:f.dataset.clientId,text,authorName:currentUser.name,createdAt:new Date().toISOString(),canDelete:true}; if(!entry.comments.some(c=>c.id===comment.id)){entry.comments.push(comment);entry.commentCount++;f.parentElement.querySelector('[data-comments]').insertAdjacentHTML('beforeend',journalComment(entry,comment));f.parentElement.querySelector('[data-comment-count]').textContent=entry.commentCount;} f.reset();delete f.dataset.clientId;f.querySelector('.form-error').textContent=''; }catch(e){if(!e.stale&&journalValid(g))f.querySelector('.form-error').textContent=e.message;}finally{b.disabled=false;} }); async function loadBaiduSettings(){ const g=appearanceGeneration;$('#baidu-admin').hidden=!isPlatformAdmin();if(!isPlatformAdmin())return; try{const [config,status]=await Promise.all([api('/api/baidu/config'),api('/api/baidu/status')]);if(g!==appearanceGeneration||activePage!=='settings')return; const f=$('#baidu-config');for(const k of ['appKey','appFolder','redirectUri'])f.elements[k].value=config[k];f.elements.secretKey.value='';f.elements.secretKey.placeholder=config.hasSecret?'已保存,留空保持原值':'填写 SecretKey';journalConfigRevision=config.revision; $('#baidu-status').textContent=status.connected?(status.reconnect?'网盘授权已失效,请重新连接。':'已连接统一网盘:'+status.account):'尚未连接百度网盘';$('#baidu-connect').disabled=!config.configured;$('#baidu-disconnect').disabled=!status.connected; }catch(e){if(!e.stale&&g===appearanceGeneration)$('#baidu-status').textContent=e.message;} } $('#baidu-config').onsubmit=async e=>{e.preventDefault();const g=appearanceGeneration,f=e.currentTarget,b=f.querySelector('button');b.disabled=true;try{await api('/api/baidu/config',{...formValues(f),revision:journalConfigRevision});if(g!==appearanceGeneration)return;f.elements.secretKey.value='';await loadBaiduSettings();$('#baidu-config-status').textContent='应用配置已保存,下一步连接百度网盘。';}catch(e){if(!e.stale&&g===appearanceGeneration)$('#baidu-config-status').textContent=e.message;}finally{b.disabled=false;}}; $('#baidu-connect').onclick=async()=>{try{const result=await api('/api/baidu/connect',{});const address=new URL(result.url);if(address.origin!=='https://openapi.baidu.com')throw Error('授权地址不正确');location.assign(result.url);}catch(e){if(!e.stale)$('#baidu-status').textContent=e.message;}}; $('#baidu-disconnect').onclick=async()=>{if(!confirm('解除本机网盘连接后,媒体将暂时无法访问;网盘文件保留。确认解除?'))return;try{await api('/api/baidu/disconnect',{});loadBaiduSettings();}catch(e){if(!e.stale)$('#baidu-status').textContent=e.message;}}; document.addEventListener('DOMContentLoaded',()=>{const result=new URLSearchParams(location.search).get('baidu');if(result){history.replaceState(null,'',location.pathname);toast(result==='ok'?'网盘授权已保存,请在设置中心查看连接状态。':'授权未完成,请检查回调地址、原登录状态,并重新连接。');}},{once:true});