Fix family publishing, concurrent edits and session consistency
This commit is contained in:
+16
-7
@@ -9,14 +9,23 @@ function clearFamilyState(){closeFamilyFullscreen();familyGeneration++;family={p
|
||||
function personName(id){return family.people.find(p=>p.id===id)?.name||'未关联';}
|
||||
function peopleOptions(blank=true){return (blank?'<option value="">尚未关联</option>':'')+family.people.map(p=>`<option value="${p.id}">${esc(p.name+(p.archived?'(已归档)':''))}</option>`).join('');}
|
||||
function fillForm(form,data){form.reset();form.querySelector('.form-error').textContent='';for(const [k,v]of Object.entries(data)){const e=form.elements[k];if(!e)continue;if(e.type==='checkbox')e.checked=!!v;else e.value=v??'';}}
|
||||
function familyFormError(form,error){
|
||||
if(error.stale)return;
|
||||
const box=form.querySelector('.form-error');box.textContent=error.message;
|
||||
if(error.status!==409||!error.message.startsWith('家谱已被更新'))return;
|
||||
box.append(document.createTextNode('。本次输入仍保留,请先复制需要保留的内容,再读取最新版本。'));
|
||||
const button=document.createElement('button');button.type='button';button.className='quiet';button.textContent='读取最新版本(覆盖本次输入)';
|
||||
button.onclick=async()=>{button.disabled=true;try{if(await loadFamily()){const id=form.elements.id.value;form.closest('dialog').close();if(form.id==='person-form')openPerson(id);else if(form.id==='relation-form')openRelation(id);else editFamilyEvent(id);}}finally{button.disabled=false;}};
|
||||
box.append(button);
|
||||
}
|
||||
async function loadFamily(resetView=false){
|
||||
const generation=++familyGeneration;
|
||||
try{const result=await api('/api/family');if(!loggedIn||generation!==familyGeneration)return;family=result;
|
||||
if(resetView===true||!family.people.some(p=>p.id===focusPerson))focusPerson=family.selfId||family.people.find(p=>!p.archived)?.id||'';
|
||||
setOptions($('#family-reference'),peopleOptions(false));if(!$('#family-reference').value&&family.selfId)$('#family-reference').value=family.selfId;
|
||||
if(!$('#family-reference').dataset.initialized&&family.selfId){$('#family-reference').value=family.selfId;$('#family-reference').dataset.initialized='true';}
|
||||
$('#family-error').textContent='';if(resetView===true){familyView='tree';$('#family-search').value='';}renderFamily();if(resetView===true)familyMap.fit();
|
||||
}catch(e){$('#family-error').textContent=e.message;}
|
||||
$('#family-error').textContent='';if(resetView===true){familyView='tree';$('#family-search').value='';}renderFamily();if(resetView===true)familyMap.fit();return true;
|
||||
}catch(e){if(!e.stale&&loggedIn&&generation===familyGeneration)$('#family-error').textContent=e.message;return false;}
|
||||
}
|
||||
function relationText(e){return personName(e.fromId)+(e.kind==='parent'?' → 子女:':' ↔ 配偶:')+personName(e.toId)+(e.active?'':'(已停用)');}
|
||||
function renderFamily(){
|
||||
@@ -25,13 +34,13 @@ function renderFamily(){
|
||||
$('#family-count').textContent=family.people.filter(p=>!p.archived).length+' 位人物 · '+family.links.filter(e=>e.active).length+' 条关系'+(term?' · 找到 '+people.length+' 位':'');
|
||||
$('#family-find').hidden=!term;$('#family-find').disabled=!people.length;
|
||||
$('#new-person').hidden=!family.canEdit;
|
||||
$('#family-people').innerHTML=people.map(p=>`<button class="person-card ${p.id===focusPerson?'selected':''} ${p.id===family.selfId?'self':''}" data-person="${p.id}" aria-pressed="${p.id===focusPerson}"><strong>${esc(p.name)} ${p.id===family.selfId?'<span class="self-badge">本人</span>':''}</strong><span>${esc(ZhaoKinship.describe(family.people,family.links,reference,p.id))}</span><small>${esc(p.birthDate||'出生待考')}${p.archived?' · 已归档':''}</small></button>`).join('')||empty(term?'没有找到这个人物':'从一个人物开始',term?'可换用姓名、别名或籍贯查找。':'可先录入自己,再逐步补齐父母、祖先和后辈。');
|
||||
$('#family-people').innerHTML=people.map(p=>`<button class="person-card ${p.id===focusPerson?'selected':''} ${p.id===family.selfId?'self':''}" data-person="${p.id}" aria-pressed="${p.id===focusPerson}"><strong>${esc(p.name)} ${p.id===family.selfId?'<span class="self-badge">本人</span>':''}</strong><span>${esc(p.id===reference&&p.id!==family.selfId?'参照人物':ZhaoKinship.describe(family.people,family.links,reference,p.id))}</span><small>${esc(p.birthDate||'出生待考')}${p.archived?' · 已归档':''}</small></button>`).join('')||empty(term?'没有找到这个人物':'从一个人物开始',term?'可换用姓名、别名或籍贯查找。':'可先录入自己,再逐步补齐父母、祖先和后辈。');
|
||||
renderFamilyTree();
|
||||
const p=family.people.find(p=>p.id===focusPerson);
|
||||
$('#family-detail').classList.toggle('is-self',p?.id===family.selfId&&!!p);
|
||||
if(!p){$('#family-detail').innerHTML='<h3>人物档案</h3><p class="description">点击图中的人物查看生平,或录入第一位人物。</p>';return;}
|
||||
const edges=family.links.filter(e=>e.fromId===p.id||e.toId===p.id);
|
||||
$('#family-detail').innerHTML=`<div class="section-head"><div><span class="eyebrow">人物档案</span><h2>${esc(p.name)}</h2></div>${family.canEdit?`<button data-edit-person="${p.id}" class="quiet">编辑人物</button>`:''}</div><p class="description">相对于 ${esc(personName(reference))}:${esc(ZhaoKinship.describe(family.people,family.links,reference,p.id))}</p><dl>${[['别名',p.alias],['性别',{male:'男',female:'女',unknown:'待确认'}[p.gender]],['在世情况',{alive:'在世',deceased:'已故',unknown:'待确认'}[p.lifeStatus]],['出生日期',ZhaoCalendar.describe(p.birthDate)],...(p.lifeStatus==='deceased'?[['去世日期',ZhaoCalendar.describe(p.deathDate)]]:[]),['籍贯 / 出生地',p.birthplace]].map(([k,v])=>`<div><dt>${k}</dt><dd>${esc(v||'待补充')}</dd></div>`).join('')}</dl><h3>生平与记载</h3><p class="family-prose">${esc(p.biography||'尚未记录生平')}</p><p class="description family-prose">${esc(p.note||'')}</p><div class="section-head"><h3>亲属关系</h3>${family.canEdit?'<button id="add-relation" class="quiet">+ 连接亲属</button>':''}</div>${edges.map(e=>`<div class="family-link"><span>${esc(relationText(e))}<small>${esc(({biological:'亲生',adoptive:'收养',step:'继亲',unspecified:''}[e.lineage]||'')+' '+(e.note||''))}</small></span>${family.canEdit?`<button class="quiet" data-edit-relation="${e.id}">编辑</button>`:''}</div>`).join('')||'<p class="description">尚未连接亲属。</p>'}`;
|
||||
$('#family-detail').innerHTML=`<div class="section-head"><div><span class="eyebrow">人物档案</span><h2>${esc(p.name)}</h2></div>${family.canEdit?`<button data-edit-person="${p.id}" class="quiet">编辑人物</button>`:''}</div><p class="description">相对于 ${esc(personName(reference))}:${esc(p.id===reference&&p.id!==family.selfId?'参照人物':ZhaoKinship.describe(family.people,family.links,reference,p.id))}</p><dl>${[['别名',p.alias],['性别',{male:'男',female:'女',unknown:'待确认'}[p.gender]],['在世情况',{alive:'在世',deceased:'已故',unknown:'待确认'}[p.lifeStatus]],['出生日期',ZhaoCalendar.describe(p.birthDate)],...(p.lifeStatus==='deceased'?[['去世日期',ZhaoCalendar.describe(p.deathDate)]]:[]),['籍贯 / 出生地',p.birthplace]].map(([k,v])=>`<div><dt>${k}</dt><dd>${esc(v||'待补充')}</dd></div>`).join('')}</dl><h3>生平与记载</h3><p class="family-prose">${esc(p.biography||'尚未记录生平')}</p><p class="description family-prose">${esc(p.note||'')}</p><div class="section-head"><h3>亲属关系</h3>${family.canEdit?'<button id="add-relation" class="quiet">+ 连接亲属</button>':''}</div>${edges.map(e=>`<div class="family-link"><span>${esc(relationText(e))}<small>${esc(({biological:'亲生',adoptive:'收养',step:'继亲',unspecified:''}[e.lineage]||'')+' '+(e.note||''))}</small></span>${family.canEdit?`<button class="quiet" data-edit-relation="${e.id}">编辑</button>`:''}</div>`).join('')||'<p class="description">尚未连接亲属。</p>'}`;
|
||||
if(typeof personEventSummary==='function')$('#family-detail').insertAdjacentHTML('beforeend',personEventSummary(p.id));
|
||||
$('#family-detail').insertAdjacentHTML('afterbegin',`${p.id===family.selfId?'<div class="family-self-heading">◎ 本人 · 当前账号关联人物</div>':''}<button type="button" id="family-show-person" class="quiet family-back-to-map">在图中定位</button>`);
|
||||
}
|
||||
@@ -44,15 +53,15 @@ function renderFamilyTree(){
|
||||
$('#family-view-tree').setAttribute('aria-pressed',String(familyView==='tree'));$('#family-view-list').setAttribute('aria-pressed',String(familyView==='list'));
|
||||
familyMap.render({...family,focusId:focusPerson,reference:$('#family-reference').value,term:$('#family-search').value.trim().toLowerCase(),showArchived:$('#family-archived').checked});
|
||||
}
|
||||
function selectFamilyPerson(id,locate=false){focusPerson=id;if(locate)familyView='tree';renderFamily();if(locate)familyMap.locate(id);if(window.innerWidth<=1000&&!document.fullscreenElement)$('#family-detail').scrollIntoView({behavior:'smooth',block:'start'});}
|
||||
function selectFamilyPerson(id,locate=false){focusPerson=id;if(locate){familyView='tree';if(family.people.find(p=>p.id===id)?.archived)$('#family-archived').checked=true;}renderFamily();if(locate)familyMap.locate(id);if(window.innerWidth<=1000&&!document.fullscreenElement)$('#family-detail').scrollIntoView({behavior:'smooth',block:'start'});}
|
||||
function openPerson(id){if(!family.canEdit)return;const p=family.people.find(p=>p.id===id)||{};fillForm($('#person-form'),{...p,revision:family.revision});initDateFields();$('#person-dialog').showModal();}
|
||||
function openRelation(id){if(!family.canEdit)return;if(family.people.length<2){toast('请先录入另一位亲属,再连接关系');return;}const f=$('#relation-form');f.elements.fromId.innerHTML=peopleOptions(false);f.elements.toId.innerHTML=peopleOptions(false);const edge=family.links.find(e=>e.id===id);fillForm(f,{...(edge||{fromId:focusPerson,active:true}),revision:family.revision});if(!edge)f.elements.toId.value=family.people.find(p=>p.id!==focusPerson)?.id||'';$('#relation-dialog').showModal();}
|
||||
for(const [name,path]of [['person','person'],['relation','link']]){
|
||||
$('#'+name+'-form').addEventListener('submit',async e=>{
|
||||
e.preventDefault();const f=e.currentTarget,b=f.querySelector('[type=submit]');b.disabled=true;
|
||||
try{const d=formValues(f);d.revision=Number(d.revision);if(name==='person'){syncDateFields(true);Object.assign(d,formValues(f));d.revision=Number(d.revision);d.archived=f.elements.archived.checked;}else d.active=f.elements.active.checked;
|
||||
const result=await api('/api/family/'+path,d);if(name==='person')focusPerson=result.item.id;f.closest('dialog').close();await loadFamily();toast('家谱已保存');
|
||||
}catch(err){f.querySelector('.form-error').textContent=err.message;}finally{b.disabled=false;}
|
||||
const result=await api('/api/family/'+path,d);if(name==='person')focusPerson=result.item.id;f.closest('dialog').close();const loaded=await loadFamily();if(loggedIn)toast(loaded?'家谱已保存':'家谱已保存,读取最新资料失败,请重新载入');
|
||||
}catch(err){familyFormError(f,err);}finally{b.disabled=false;}
|
||||
});
|
||||
}
|
||||
async function loadUsers(){
|
||||
|
||||
Reference in New Issue
Block a user