2026-10-04 19:22:31 +08:00
|
|
|
'use strict';
|
|
|
|
|
pageInfo.journal=['个人记事','一言一照,都是阅历。私藏或分享,由自己决定。'];
|
|
|
|
|
let journalGeneration=0,journalData=null,journalPage=0,journalUpload=false,journalRequest=null,journalRecorder=null,journalTracks=null,journalVoice=[],journalVoiceTimer=null,journalConfigRevision=0;
|
2026-10-04 20:03:44 +08:00
|
|
|
const journalDraftMedia=new Set(),journalSelectedMedia=new Set();
|
2026-10-04 19:22:31 +08:00
|
|
|
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'}:{})});}
|
2026-10-04 20:03:44 +08:00
|
|
|
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 `<figure class="journal-attachment"><button type="button" class="journal-photo" data-preview-media="${id}" aria-label="放大预览 ${name}"><img src="${src}" alt="${name}" loading="lazy" decoding="async"><span class="journal-photo-hint">点击放大</span></button><span class="journal-media-state" role="status">照片加载中…</span><button type="button" class="quiet" data-retry-media hidden>重新加载照片</button></figure>`;
|
|
|
|
|
const tag=m.mime.startsWith('audio/')?'audio':'video';
|
|
|
|
|
return `<figure class="journal-attachment journal-${tag}">${tag==='audio'?'<div class="journal-audio-label"><span aria-hidden="true">♪</span> 语音与音频</div>':''}<${tag} controls preload="metadata" playsinline src="${src}" aria-label="${name}"></${tag}><span class="journal-media-state" role="status">正在加载${tag==='audio'?'音频':'视频'}…</span><button type="button" class="quiet" data-retry-media hidden>重新加载</button></figure>`;
|
|
|
|
|
}
|
|
|
|
|
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');});
|
2026-10-04 19:22:31 +08:00
|
|
|
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();
|
2026-10-04 20:03:44 +08:00
|
|
|
for(const id of ['journal-list','journal-upload-list','journal-library-list','journal-preview-content'])releaseJournalMedia($('#'+id));
|
|
|
|
|
$('#journal-preview').close();
|
2026-10-04 19:22:31 +08:00
|
|
|
$('#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='';}
|
2026-10-04 20:03:44 +08:00
|
|
|
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();}
|
2026-10-04 19:22:31 +08:00
|
|
|
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='<option value="">请选择人物</option>'+Object.entries(result.people).map(([id,name])=>`<option value="${esc(id)}">${esc(name)}</option>`).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;}
|
|
|
|
|
}
|
2026-10-04 20:03:44 +08:00
|
|
|
function journalUploadCard(m,library=false){
|
|
|
|
|
const id=esc(m.id),ready=m.status==='ready';
|
|
|
|
|
return `<div class="journal-upload-card" data-upload-card="${id}"><${library?'div':'label'} class="journal-upload-select">${library?'':`<input type="checkbox" value="${id}" ${ready?'':'disabled'} ${journalSelectedMedia.has(m.id)?'checked':''}>`}<span>${esc(m.name)}<small>${Math.ceil(m.size/1024)} KB · ${ready?'已存百度网盘':'等待继续上传'}</small></span></${library?'div':'label'}>${ready?journalMediaView(m):`<p class="description">${esc(m.error||'上传尚未完成,请选择原文件继续。')}</p>`}<div class="journal-card-actions">${ready?`<button type="button" class="quiet" data-preview-media="${id}">预览</button>${library?`<button type="button" class="quiet" data-use-upload="${id}">用于本条</button>`:`<button type="button" class="quiet" data-detach-upload="${id}">从本条移除</button>`}`:`<label class="quiet journal-resume">继续上传<input type="file" data-journal-resume="${id}"></label>`}<button type="button" class="quiet" data-remove-upload="${id}">删除</button></div></div>`;
|
|
|
|
|
}
|
2026-10-04 19:22:31 +08:00
|
|
|
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);
|
2026-10-04 20:03:44 +08:00
|
|
|
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('')||'<p class="description">暂无其他附件。</p>'):'';
|
|
|
|
|
const trash=journalData?.trash||[];$('#journal-trash').hidden=!trash.length;
|
|
|
|
|
$('#journal-trash-count').textContent=String(trash.length);
|
|
|
|
|
$('#journal-trash-list').innerHTML=trash.map(m=>`<div class="journal-trash-row"><span>${esc(m.name)}</span><button type="button" class="quiet" data-restore-upload="${esc(m.id)}">恢复</button></div>`).join('');
|
|
|
|
|
bindJournalMedia($('#journal-upload-list'));bindJournalMedia($('#journal-library-list'));
|
2026-10-04 19:22:31 +08:00
|
|
|
}
|
2026-10-04 20:03:44 +08:00
|
|
|
$('#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);}});
|
2026-10-04 19:22:31 +08:00
|
|
|
function renderJournalEntries(){
|
2026-10-04 20:03:44 +08:00
|
|
|
releaseJournalMedia($('#journal-list'));
|
|
|
|
|
$('#journal-list').innerHTML=journalData.items.map(e=>`<article class="panel journal-entry" data-entry="${esc(e.id)}"><div class="section-head"><div><button class="quiet journal-author" data-journal-person="${esc(e.ownerId)}">${esc(e.authorName)}</button><small>${esc(e.occurredOn)} · ${esc(e.category)} · ${e.visibility==='private'?'仅自己可见':'当前家庭/场所'}</small></div>${e.canEdit?`<div><button class="quiet" data-journal-edit="${esc(e.id)}">编辑</button><button class="quiet" data-journal-delete="${esc(e.id)}">删除</button></div>`:''}</div><p class="journal-text">${esc(e.text)}</p><div class="journal-media">${e.media.map(m=>`<div>${journalMediaView(m)}<p class="journal-media-caption">${esc(m.name)} <a href="${esc(journalMediaUrl(m.id,true))}">下载原文件</a></p></div>`).join('')}</div><div class="journal-reactions"><button class="quiet" data-journal-like="${esc(e.id)}" aria-pressed="${e.liked}">${e.liked?'♥ 已赞':'♡ 点赞'} · ${e.likes}</button><button class="quiet" data-journal-person="${esc(e.ownerId)}">查看个人阅历</button></div><details class="journal-comments"><summary>评论 <span data-comment-count>${e.commentCount}</span></summary><div data-comments>${e.comments.map(c=>journalComment(e,c)).join('')}</div><form data-journal-comment="${esc(e.id)}"><label>写评论<input name="text" maxlength="500" required autocomplete="off"></label><button type="submit">发送</button><span class="form-error" role="status"></span></form></details></article>`).join('');
|
|
|
|
|
bindJournalMedia($('#journal-list'));
|
2026-10-04 19:22:31 +08:00
|
|
|
}
|
|
|
|
|
function journalComment(entry,c){return `<p class="journal-comment"><strong>${esc(c.authorName)}</strong> ${esc(c.text)} <small>${esc(new Date(c.createdAt).toLocaleString())}</small>${c.canDelete?` <button class="quiet" data-delete-comment="${esc(c.id)}" data-journal-entry="${esc(entry.id)}">删除评论</button>`:''}</p>`;}
|
|
|
|
|
$('#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<file.size;){
|
|
|
|
|
if(!journalValid(g)||!journalUpload)return;
|
|
|
|
|
journalRequest=new AbortController();
|
|
|
|
|
const response=await fetch('/api/journal/chunk?'+new URLSearchParams({id:task.id,offset:String(offset)}),{method:'POST',headers:{'Content-Type':'application/octet-stream','X-Household-Id':state.familyId},body:file.slice(offset,Math.min(offset+524288,file.size)),signal:journalRequest.signal});
|
|
|
|
|
const result=await response.json();if(!response.ok)throw Error(result.error||'上传未完成');
|
|
|
|
|
if(!journalValid(g)||!journalUpload)return;
|
|
|
|
|
if(result.received<=offset)throw Error('上传进度未前进,请选择原文件继续');offset=result.received;journalMessage('正在上传百度网盘:'+Math.round(offset/file.size*95)+'%');
|
|
|
|
|
}
|
|
|
|
|
await api('/api/journal/finish',{id:task.id});if(!journalValid(g)||!journalUpload)return;
|
2026-10-04 20:03:44 +08:00
|
|
|
journalUpload=false;journalDraftMedia.add(task.id);journalSelectedMedia.add(task.id);await loadJournal();journalMessage('附件已保存到百度网盘。填写内容和可见范围后保存记事。');
|
2026-10-04 19:22:31 +08:00
|
|
|
}catch(e){if(!e.stale&&journalValid(g)){journalMessage(e.name==='AbortError'?'已暂停上传,可选择原文件继续。':e.message);journalUpload=false;const message=$('#journal-status').textContent;await loadJournal();journalMessage(message);}}
|
|
|
|
|
finally{journalBusy(false);journalRequest=null;if(activePage==='journal'){$('#journal-save').disabled=false;$('#journal-files').disabled=!journalData?.connection.connected;$('#journal-cancel-upload').hidden=true;}}
|
|
|
|
|
}
|
|
|
|
|
$('#journal-files').onchange=e=>{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;}
|
2026-10-04 20:03:44 +08:00
|
|
|
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;
|
2026-10-04 19:22:31 +08:00
|
|
|
}
|
|
|
|
|
const id=b.dataset.journalLike||b.dataset.journalDelete||b.dataset.journalEntry;
|
2026-10-04 20:03:44 +08:00
|
|
|
if(!id&&!b.dataset.removeUpload&&!b.dataset.restoreUpload)return;b.disabled=true;
|
2026-10-04 19:22:31 +08:00
|
|
|
try{
|
2026-10-04 20:03:44 +08:00
|
|
|
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;
|
|
|
|
|
}
|
2026-10-04 19:22:31 +08:00
|
|
|
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});
|