'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 journalDraftMedia=new Set(),journalSelectedMedia=new Set(); 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 releaseJournalMedia(root){ root.querySelectorAll('audio,video').forEach(v=>{v.pause();v.removeAttribute('src');v.load();}); root.querySelectorAll('img').forEach(img=>img.removeAttribute('src')); } function journalMediaView(m){ const name=esc(m.name),id=esc(m.id),src=esc(journalMediaUrl(m.id)); if(m.mime.startsWith('image/'))return `
照片加载中…
`; const tag=m.mime.startsWith('audio/')?'audio':'video'; return `
${tag==='audio'?'
语音与音频
':''}<${tag} controls preload="metadata" playsinline src="${src}" aria-label="${name}">正在加载${tag==='audio'?'音频':'视频'}…
`; } function bindJournalMedia(root){ root.querySelectorAll('.journal-attachment').forEach(box=>{ const media=box.querySelector('img,audio,video'),message=box.querySelector('.journal-media-state'),retry=box.querySelector('[data-retry-media]'); const ready=()=>{message.textContent='';retry.hidden=true;box.classList.remove('media-error');}; media.addEventListener(media.tagName==='IMG'?'load':'loadedmetadata',ready); media.addEventListener('error',()=>{message.textContent=media.tagName==='IMG'?'照片暂未加载,请重试。':'暂时无法播放,请重试或下载原文件。';retry.hidden=false;box.classList.add('media-error');}); if(media.tagName==='IMG'&&media.complete&&media.naturalWidth)ready(); if(media.tagName!=='IMG'){ if(media.readyState>=1)ready(); media.addEventListener('waiting',()=>{message.textContent='正在缓冲…';}); media.addEventListener('playing',ready);media.addEventListener('canplay',ready); media.addEventListener('play',()=>{$$('#journal-list audio,#journal-list video,#journal-upload-list audio,#journal-upload-list video,#journal-library-list audio,#journal-library-list video,#journal-preview audio,#journal-preview video').forEach(other=>{if(other!==media)other.pause();});}); } }); } function previewJournalMedia(id){ const all=[...(journalData?.uploads||[]),...(journalData?.trash||[]),...(journalData?.items||[]).flatMap(e=>e.media)]; const media=all.find(m=>m.id===id&&m.status==='ready');if(!media)return; const dialog=$('#journal-preview');releaseJournalMedia($('#journal-preview-content')); $('#journal-preview-title').textContent=media.name;$('#journal-preview-download').href=journalMediaUrl(id,true); $('#journal-preview-content').innerHTML=journalMediaView(media);bindJournalMedia($('#journal-preview-content'));if(!dialog.open)dialog.showModal(); } $('#journal-preview-close').onclick=()=>$('#journal-preview').close(); $('#journal-preview').addEventListener('close',()=>{releaseJournalMedia($('#journal-preview-content'));$('#journal-preview-content').replaceChildren();$('#journal-preview-download').removeAttribute('href');}); 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(); for(const id of ['journal-list','journal-upload-list','journal-library-list','journal-preview-content'])releaseJournalMedia($('#'+id)); $('#journal-preview').close(); $('#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();journalDraftMedia.clear();journalSelectedMedia.clear();$('#journal-library-list').replaceChildren();$('#journal-trash-list').replaceChildren();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();stopJournalVoice();journalDraftMedia.clear();journalSelectedMedia.clear();$('#journal-library').open=false;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 journalUploadCard(m,library=false){ const id=esc(m.id),ready=m.status==='ready'; return `
<${library?'div':'label'} class="journal-upload-select">${library?'':``}${esc(m.name)}${Math.ceil(m.size/1024)} KB · ${ready?'已存百度网盘':'等待继续上传'}${ready?journalMediaView(m):`

${esc(m.error||'上传尚未完成,请选择原文件继续。')}

`}
${ready?`${library?``:``}`:``}
`; } function renderJournalUploads(){ 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); releaseJournalMedia($('#journal-upload-list'));releaseJournalMedia($('#journal-library-list')); $('#journal-upload-list').innerHTML=available.filter(m=>journalDraftMedia.has(m.id)).map(m=>journalUploadCard(m)).join(''); const library=(journalData?.uploads||[]).filter(m=>!journalDraftMedia.has(m.id)); $('#journal-library-count').textContent=String(library.length); $('#journal-library-list').innerHTML=$('#journal-library').open?(library.map(m=>journalUploadCard(m,true)).join('')||'

暂无其他附件。

'):''; const trash=journalData?.trash||[];$('#journal-trash').hidden=!trash.length; $('#journal-trash-count').textContent=String(trash.length); $('#journal-trash-list').innerHTML=trash.map(m=>`
${esc(m.name)}
`).join(''); bindJournalMedia($('#journal-upload-list'));bindJournalMedia($('#journal-library-list')); } $('#journal-library').addEventListener('toggle',renderJournalUploads); $('#journal-upload-list').addEventListener('change',e=>{if(e.target.type==='checkbox'){if(e.target.checked)journalSelectedMedia.add(e.target.value);else journalSelectedMedia.delete(e.target.value);}}); function renderJournalEntries(){ releaseJournalMedia($('#journal-list')); $('#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=>`
${journalMediaView(m)}

${esc(m.name)} 下载原文件

`).join('')}
评论 ${e.commentCount}
${e.comments.map(c=>journalComment(e,c)).join('')}
`).join(''); bindJournalMedia($('#journal-list')); } 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];journalDraftMedia.clear();journalSelectedMedia.clear();e.mediaIds.forEach(id=>{journalDraftMedia.add(id);journalSelectedMedia.add(id);});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.useUpload){journalDraftMedia.add(b.dataset.useUpload);journalSelectedMedia.add(b.dataset.useUpload);renderJournalUploads();return;} if(b.dataset.detachUpload){journalDraftMedia.delete(b.dataset.detachUpload);journalSelectedMedia.delete(b.dataset.detachUpload);renderJournalUploads();return;} if(b.dataset.previewMedia){previewJournalMedia(b.dataset.previewMedia);return;} if(b.hasAttribute('data-retry-media')){ const box=b.closest('.journal-attachment'),media=box.querySelector('img,audio,video'); b.hidden=true;box.classList.remove('media-error');box.querySelector('.journal-media-state').textContent='正在重新加载…'; if(media.tagName==='IMG'){const src=media.src;media.removeAttribute('src');media.src=src;}else media.load();return; } const id=b.dataset.journalLike||b.dataset.journalDelete||b.dataset.journalEntry; if(!id&&!b.dataset.removeUpload&&!b.dataset.restoreUpload)return;b.disabled=true; try{ if(b.dataset.removeUpload||b.dataset.restoreUpload){ if(journalUpload){journalMessage('请等待当前上传完成,或先暂停上传。');return;} const restore=!!b.dataset.restoreUpload; await api(restore?'/api/journal/restore-upload':'/api/journal/remove-upload',{id:b.dataset.restoreUpload||b.dataset.removeUpload}); if(!journalValid(g))return; const result=await api('/api/journal?mode=mine');if(!journalValid(g))return; journalData.uploads=result.uploads;journalData.trash=result.trash;if(!restore){journalDraftMedia.delete(b.dataset.removeUpload);journalSelectedMedia.delete(b.dataset.removeUpload);} // Keep an editor's existing references current without replacing its draft or feed players. for(const entry of journalData.items)for(const media of entry.media){const current=[...result.uploads,...result.trash].find(m=>m.id===media.id);if(current)Object.assign(media,current);} renderJournalUploads();journalMessage(restore?'附件已恢复,可重新勾选使用。':'已移到“已删除附件”,可以恢复。已发布的记事和网盘原文件保留。');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});