diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..3e6ec76 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,10 @@ +# 第三方库原样保存:不做行尾转换 +# +# 为什么要写这个:本机 core.autocrlf 开着,`.min.js` 这类文本文件被 Git 检出时 +# 会被转成 CRLF(`git add` 时会警告"LF will be replaced by CRLF")。 +# 对第三方库这是**无谓的改写** —— 一来哈希与上游不一致, +# 二来部署包里带的就是改写后的版本,将来想核对「是不是原版」就没了基准。 +# 标记成 binary(-text)后 Git 原样存、原样取。 +assets/vendor/** -text +*.min.js -text +*.min.css -text \ No newline at end of file diff --git a/assets/css/app.css b/assets/css/app.css index 28dc681..8690424 100644 --- a/assets/css/app.css +++ b/assets/css/app.css @@ -21,6 +21,19 @@ --radius-s: 10px; --shadow: 0 1px 2px rgba(44, 35, 29, .05); --shadow-l: 0 4px 16px rgba(44, 35, 29, .08); + + /* ---- 动作按钮的统一度量 ---- + 用户提过三次「设置按钮和记一场电影按钮高度要一致」。为杜绝再犯, + 这里把按钮高度拆成 4 个变量,并算出唯一的高度: + --act-h = 上下内边距 + 行高 + 上下边框 = 9*2 + 14*1.7 + 1*2 = 43.8px + 文字按钮靠 padding + line-height 自然撑到这个高度; + **只有图标的方形按钮**(.btn-icon)显式写高度,但取的是同一个 --act-h, + 这样两边永远相等,改一处就同步。 */ + --act-pad-y: 9px; + --act-pad-x: 16px; + --act-font: 14px; + --act-line: 1.7; + --act-h: calc(var(--act-pad-y) * 2 + var(--act-font) * var(--act-line) + 1px * 2); } * { box-sizing: border-box; } @@ -100,15 +113,41 @@ a:hover { text-decoration: underline; } border-radius: 0 0 8px 8px; color: var(--muted); } .menu-list .menu-exit:hover { color: var(--brand); } -/* ---- 观影页的「设置」下拉(v11):一个齿轮图标 +
菜单 ---- */ -.movie-set { position: relative; } +/* ---- 观影页的「设置」下拉:图标按钮 + 永远贴右 ---- + 用户明确要求(2026-10-01): + · 去掉「设置」二字,只留**三条横线**图标(☰); + · 在**所有页面都排在最右侧**(位置统一); + · 点开后的每条条目,**文字前都要有图标**; + · 除设置外,其它按钮都要**有颜色**,跟设置区分开。 + 贴右的做法:`margin-left:auto` 把它推到动作区最右端 —— 这样无论各页把片段 + require 在 actions 的哪个位置(有的写在最前、有的写在最后),它都贴右, + 不必逐页调顺序,也就不会有"某个页面漏了"的情况。 */ +.movie-set { position: relative; margin-left: auto; } .movie-set > summary { - list-style: none; cursor: pointer; display: inline-flex; align-items: center; gap: 6px; + list-style: none; cursor: pointer; display: inline-flex; align-items: center; + justify-content: center; gap: 6px; } .movie-set > summary::-webkit-details-marker { display: none; } .movie-set[open] > summary { border-color: var(--brand); color: var(--brand); } -/* 观影设置菜单是顶栏菜单的复用:位置从右上角改成按钮下方左对齐 */ -.movie-set .menu-list { left: 0; right: auto; top: calc(100% + 8px); } + +/* 方形图标按钮:只有图标、无文字。高度**必须**取 --act-h, + 与「记一场电影」等实心按钮同高(那个高度由 --act-pad-y/--act-font/--act-line 推出)。 + ⚠️ 别在这里另写一个数字(曾写死 40px,比文字按钮的 43.8px 矮了 3.8px,被用户连续投诉)。 */ +.btn-icon { + background: #fff; color: var(--ink); border: 1px solid var(--line); + padding: 0 !important; width: var(--act-h); height: var(--act-h); min-width: var(--act-h); + display: inline-flex; align-items: center; justify-content: center; + line-height: 1; +} +.btn-icon:hover { background: #FAF7F1; color: var(--brand); border-color: var(--brand); } +.btn-icon svg { display: block; } + +/* 观影设置菜单:从右上角改成**按钮下方右对齐**(按钮贴右,菜单也贴右才不会跑出屏外) */ +.movie-set .menu-list { left: auto; right: 0; top: calc(100% + 8px); } +.movie-set .menu-list a { display: flex; align-items: center; gap: 10px; } +.movie-set .menu-list a .mi { flex: 0 0 16px; color: var(--muted); } +.movie-set .menu-list a:hover .mi, +.movie-set .menu-list a.on .mi { color: currentColor; } .movie-set .menu-list a.on { color: var(--brand); background: var(--line-2); } .link-quiet { color: var(--muted); font-size: 14px; } .link-quiet:hover { color: var(--brand); } @@ -124,21 +163,10 @@ a:hover { text-decoration: underline; } .page-head h1 { font-size: 22px; font-weight: 600; margin: 0; } .page-head .sub { color: var(--muted); font-size: 14px; } .page-head .actions { margin-left: auto; display: flex; gap: 8px; align-items: center; } -/* ---- 页头动作区:所有按钮一律同尺寸 ---- - ⚠️ 用户明确提过两次:「设置按钮和记一场电影按钮高度要一致,现在不好看」 - 「返回等按钮和记一场电影等按钮的大小高度的统一问题」。 - 根因是页头里混用了 .btn 与 .btn-sm 两档(padding 9px16px vs 5px12px), - 并排就一高一矮。这里**强制**让页头动作区的按钮回到基础档: - · 按钮(.btn / .btn-ghost / .btn-danger,含
的 summary)统一 padding 与字号; - · 表单包裹的按钮(删除那种
\n \n \n\n ${e.linkExistingRelative?De(e):""}\n\n
\n ${function(e){return`\n
\n \n
\n `}(e)}\n\n ${function(e){return`\n
\n \n
\n `}(e)}\n \n `}function Re(e){return e.editable?`\n
\n ${e.gender_field.options.map(t=>`\n \n `).join("")}\n
\n `:""}function He(e){if(!e.editable)return function(){let t="";return e.fields.forEach(e=>{var n;if("rel_reference"===e.type){if(!e.initial_value)return;t+=`\n
\n ${e.label} - ${e.rel_label}\n ${e.initial_value||""}\n
`}else if("select"===e.type){const i=e;if(!e.initial_value)return;t+=`\n
\n ${i.label}\n ${(null===(n=i.options.find(e=>e.value===i.initial_value))||void 0===n?void 0:n.label)||""}\n
`}else t+=`\n
\n ${e.label}\n ${e.initial_value||""}\n
`}),t}();let t="";return e.fields.forEach(e=>{if("text"===e.type)t+=`\n
\n \n \n
`;else if("textarea"===e.type)t+=`\n
\n \n \n
`;else if("select"===e.type){const n=e;t+=`\n
\n \n \n
`}else"rel_reference"===e.type&&(t+=`\n
\n \n \n
`)}),t}function De(e){return`\n
\n
\n \n
\n `}function Fe(){return'\n \n ×\n \n '}function Oe(e,t){return Te(e,t)}function Le(e,t){return Te(e,t)}function Te(e,t){const n=function(e){return"new_rel"in e}(e),i=document.createElement("div");return function r(){const s=n?function(e){return` \n
\n \n \n ×\n \n \n

${e.title}

\n ${Re(e)}\n\n ${He(e)}\n \n
\n \n \n
\n\n ${e.linkExistingRelative?De(e):""}\n
\n `}(e):Ae(e);i.innerHTML=s,function(e,t,n,i){const r=e.querySelector("form");r.addEventListener("submit",t.onSubmit);r.querySelector(".f3-cancel-btn").addEventListener("click",s);function s(){t.editable=!1,t.onCancel&&t.onCancel(),i()}r.querySelector(".f3-close-btn").addEventListener("click",n)}(i,e,t,r),n?function(e,t){const n=e.querySelector("form"),i=n.querySelector(".f3-link-existing-relative select");i&&i.addEventListener("change",t.linkExistingRelative.onSelect)}(i,e):function(e,t,n){const i=e.querySelector("form"),r=i.querySelector(".f3-edit-btn");r&&r.addEventListener("click",l);const s=i.querySelector(".f3-delete-btn");s&&t.onDelete&&s.addEventListener("click",t.onDelete);const a=i.querySelector(".f3-add-relative-btn");a&&t.addRelative&&a.addEventListener("click",()=>{t.addRelativeActive?t.addRelativeCancel():t.addRelative(),t.addRelativeActive=!t.addRelativeActive,n()});const o=i.querySelector(".f3-remove-relative-btn");o&&t.removeRelative&&o.addEventListener("click",()=>{t.removeRelativeActive?t.removeRelativeCancel():t.removeRelative(),t.removeRelativeActive=!t.removeRelativeActive,n()});const d=i.querySelector(".f3-link-existing-relative select");d&&d.addEventListener("change",t.linkExistingRelative.onSelect);function l(){t.editable=!t.editable,n()}}(i,e,r);e.onFormCreation&&e.onFormCreation({cont:i,form_creator:e})}(),i}function Pe(e,t,n){let i=[],r=-1;return{changed:function(){r0}function o(t){const i=e.getMainId();(t=JSON.parse(JSON.stringify(t))).find(e=>e.id===i)||e.updateMainId(t.main_id),e.updateData(t),n()}}function qe(e,t){const n=i.select(e).append("div").attr("class","f3-history-controls");e.insertBefore(n.node(),e.firstChild);const r=n.append("button").attr("class","f3-back-button").on("click",()=>{t.back(),a()}),s=n.append("button").attr("class","f3-forward-button").on("click",()=>{t.forward(),a()});return r.html(ve()),s.html(ye()),{back_btn:r.node(),forward_btn:s.node(),updateButtons:a,destroy:function(){i.select(e).select(".f3-history-controls").remove()}};function a(){r.classed("disabled",!t.canBack()),s.classed("disabled",!t.canForward()),t.canBack()||t.canForward()?n.style("opacity",1).style("pointer-events","auto"):n.style("opacity",0).style("pointer-events","none")}}var je=Object.freeze({__proto__:null,addNewPerson:function({data_stash:e,datum:t}){e.push(t)},calculateDelay:c,calculateTreeFit:b,cardChangeMain:q,cardComponentSetup:function(e){const t=()=>e.querySelector("#htmlSvg");return A(e,{onZoom:D(()=>e.querySelector("svg .view"),()=>e.querySelector("#htmlSvg .cards_view"))}),i.select(t()).append("div").attr("class","cards_view_fake").style("display","none"),O(t)},cardToMiddle:C,checkIfConnectedToFirstPerson:V,checkIfRelativesConnectedWithoutPerson:j,cleanupDataJson:J,createFormEdit:Le,createFormNew:Oe,createHistory:Pe,createHistoryControls:qe,createNewPerson:d,createNewPersonWithGenderFromRel:function({data:e,rel_type:t,rel_datum:n}){const i=function(e,t){return["daughter","mother"].includes(t)||"spouse"===t&&"M"===e.data.gender?"F":"M"}(n,t);return d({data:e=Object.assign(e||{},{gender:i})})},deletePerson:Z,getCurrentZoom:$,htmlContSetup:R,isAllRelativeDisplayed:l,manualZoom:x,moveToAddToAdded:function(e,t){return delete e.to_add,e},onDeleteSyncRelReference:U,removeToAdd:B,removeToAddFromData:W,setupZoom:S,submitFormData:z,syncRelReference:N,treeFit:w,zoomTo:k});function Ve({d:e,card_dim:t,card_display:n}){return{template:`\n \n \n ${ze({d:e,card_dim:t,card_display:n}).template}\n \n `}}function ze({d:e,card_dim:t,card_display:n}){return{template:`\n \n \n \n \n ${Array.isArray(n)?n.map(t=>`${t(e.data)}`).join("\n"):n(e.data)}\n \n \n \n \n \n `}}function Ne({d:e,card_dim:t,is_new:n}){return{template:`\n \n `}}function Ue(e,t,n){const i=document.createElementNS("http://www.w3.org/2000/svg","g");i.innerHTML=e,n?t.insertBefore(i,t.firstChild):t.appendChild(i)}const Be={miniTree:function(e,t){if(e.data.to_add)return;const n=t.card_dim;if(e.all_rels_displayed)return;const r=i.create("svg:g").html(function({d:e,card_dim:t}){return{template:`\n \n \n \n \n \n \n \n \n \n \n `}}({d:e,card_dim:n}).template);return r.on("click",function(n){n.stopPropagation(),t.onMiniTreeClick?t.onMiniTreeClick.call(this,n,e):q(t.store,{d:e})}),r.node()},cardBody:function(e,t){const n=t.card_dim,r=i.create("svg:g").html(Ve({d:e,card_dim:n,card_display:t.card_display}).template);return r.on("click",function(n){n.stopPropagation(),t.onCardClick?t.onCardClick.call(this,n,e):q(t.store,{d:e})}),r.node()},cardImage:function(e,t){if(e.data.to_add)return;const n=t.card_dim;return i.create("svg:g").html(function({d:e,image:t,card_dim:n,maleIcon:i,femaleIcon:r}){return{template:`\n \n ${t?``:(e.data.data.gender,e.data.data.gender,`\n \n \n \n \n \n \n `)} \n \n `}}({d:e,image:e.data.data.avatar||null,card_dim:n,maleIcon:void 0,femaleIcon:void 0}).template).node()}};function Ze(e,t,n=!1){e&&(n?t.insertBefore(e,t.firstChild):t.appendChild(e))}function Je(e){const t="default"===e.style?s:"imageCircleRect"===e.style?function(t){return t.data.data[e.cardImageField]?a(t):o(t)}:"imageCircle"===e.style?a:"imageRect"===e.style?function(e){return n(e)}:"rect"===e.style?o:s;return function(n){if(this.innerHTML=`\n
\n ${e.mini_tree?function(t){return e.mini_tree?t.data.to_add||t.data._new_rel_data||t.all_rels_displayed?"":`
${be()}
`:""}(n):""}\n ${e.cardInnerHtmlCreator&&!n.data._new_rel_data?e.cardInnerHtmlCreator(n):t(n)}\n
\n `,this.querySelector(".card").addEventListener("click",t=>e.onCardClick(t,n)),e.onCardUpdate&&e.onCardUpdate.call(this,n),e.onCardMouseenter&&i.select(this).select(".card").on("mouseenter",t=>e.onCardMouseenter(t,n)),e.onCardMouseleave&&i.select(this).select(".card").on("mouseleave",t=>e.onCardMouseleave(t,n)),n.duplicate&&function(e,t){i.select(e).on("mouseenter",n=>{i.select(e.closest(".cards_view")).selectAll(".card_cont").select(".card").classed("f3-card-duplicate-hover",e=>e.data.id===t.data.id)}),i.select(e).on("mouseleave",t=>{i.select(e.closest(".cards_view")).selectAll(".card_cont").select(".card").classed("f3-card-duplicate-hover",!1)})}(this,n),e.duplicate_branch_toggle&&function(e,t,n,r){if(!t.hasOwnProperty("_toggle"))return;const s=e.querySelector(".card"),a=s.querySelector(".card-inner"),o=e.querySelector(".card").offsetWidth;let d,l;e.querySelector(".card").offsetHeight;const c={};if(t.spouse){const e=t.spouse,i=e.data.main?"main":e.parent.data.id;if(d=e.data._tgdp_sp[i][t.data.id]<0,c.top=60,c.left=t.sx-t.x-30+o/2,n&&(c.top=t.sy-t.x+4,c.left=o/2+4,Math.abs(t.sx-t.y)<10&&(c.left=o-4)),l=e._toggle_id_sp?e._toggle_id_sp[t.data.id]:-1,-1===l)return}else{const e=t.data.main?"main":t.parent.data.id;d=t.data._tgdp[e]<0,c.top=-65,c.left=o/2-30,n&&(c.top=5,c.left=-55),l=t._toggle_id}if(a.style.zIndex=1,i.select(s).append("div").attr("class","f3-toggle-div").attr("style","cursor: pointer; width: 60px; height: 60px;position: absolute; z-index: -1;").style("top",c.top+"px").style("left",c.left+"px").on("click",e=>{if(e.stopPropagation(),t.spouse){const e=t.spouse,n=e.data.main?"main":e.parent.data.id;e.data._tgdp_sp[n].hasOwnProperty(t.data.id)||console.error("no toggle",t,e);let i=e.data._tgdp_sp[n][t.data.id];i=i<0?(new Date).getTime():-(new Date).getTime(),e.data._tgdp_sp[n][t.data.id]=i}else{const e=t.data.main?"main":t.parent.data.id;let n=t.data._tgdp[e];n=n<0?(new Date).getTime():-(new Date).getTime(),t.data._tgdp[e]=n}r()}).append("div").html(d?xe():Ce()).select("svg").classed("f3-toggle-icon",!0).style("color",d?"#585656":"#61bf52").style("padding","0"),i.select(s).select(".f3-toggle-icon .f3-small-circle").style("fill","#fff"),i.select(s).select(".f3-toggle-icon").append("text").attr("transform",d?"translate(10.6, 14.5)":"translate(4.1, 14.5)").attr("fill","#fff").attr("font-size","7px").text("C"+l),d){let e;e=t.is_ancestry?n?"translate(5, -30)rotate(-90)":"translate(0, -10)":n?"translate(11, -22)rotate(90)":"translate(-7, -32)rotate(180)",i.select(s).select(".f3-toggle-div").insert("div").html(be()).select("svg").attr("style","position: absolute; z-index: -1;top: 0;left: 0;border-radius: 0;").style("width","66px").style("height","112px").attr("transform",e).attr("viewBox","0 0 72 125").select("line").attr("y1",t.is_ancestry?"62":"92")}}(this,n,e.store.state.is_horizontal,e.store.updateTree),location.origin.includes("localhost")&&(n.__node=this.querySelector(".card"),n.__label=n.data.data["first name"],n.data.to_add)){const e=n.spouse||n.coparent||null;e&&i.select(this).select(".card").attr("data-to-add",e.data.data["first name"])}};function n(t){return`\n
\n ${t.data.data[e.cardImageField]?``:c(t)}\n
${r(t)}
\n ${t.duplicate?u(t):""}\n
\n `}function r(t){return t.data._new_rel_data?function(e){const t=[];t.push(`data-rel-type="${e.data._new_rel_data.rel_type}"`),["son","daughter"].includes(e.data._new_rel_data.rel_type)&&t.push(`data-other-parent-id="${e.data._new_rel_data.other_parent_id}"`);return`
${e.data._new_rel_data.label}
`}(t):t.data.to_add?`
${e.empty_card_label||"ADD"}
`:t.data.unknown?`
${e.unknown_card_label||"UNKNOWN"}
`:`\n ${e.card_display.map(e=>`
${e(t.data)}
`).join("")}\n `}function s(e){return n(e)}function a(t){return function(t){return`\n
\n ${t.data.data[e.cardImageField]?``:c(t)}\n
${r(t)}
\n ${t.duplicate?u(t):""}\n
\n `}(t)}function o(e){return function(e){return`\n
\n ${r(e)}\n ${e.duplicate?u(e):""}\n
\n `}(e)}function d(){let t='style="';return e.card_dim.w||e.card_dim.h?(t+=`width: ${e.card_dim.w}px; min-height: ${e.card_dim.h}px;`,e.card_dim.height_auto?t+="height: auto;":t+=`height: ${e.card_dim.h}px;`,t+='"',t):""}function l(){let t='style="position: relative;';return e.card_dim.img_w||e.card_dim.img_h||e.card_dim.img_x||e.card_dim.img_y?(t+=`width: ${e.card_dim.img_w}px; height: ${e.card_dim.img_h}px;`,t+=`left: ${e.card_dim.img_x}px; top: ${e.card_dim.img_y}px;`,t+='"',t):""}function c(t){return t.data._new_rel_data?`
${_e()}
`:`
${e.defaultPersonIcon?e.defaultPersonIcon(t):we()}
`}function u(e){return`
x${e.duplicate}
`}}function We(e,t){function n(e,t,n){const{w:i,h:r}=e,s=t,a=n||[],o=e=>a.includes(e);return`${o("lx")?"M0,0":`M0,${s} Q 0,0 5,0`} ${o("rx")?`H${i}`:`H${i-s} Q ${i},0 ${i},5`} ${o("ry")?`V${r}`:`V${r-s} Q ${i},${r} ${i-s},${r}`} ${o("ly")?"H0":`H${s} Q 0,${r} 0,${r-s}`} z`}e.querySelector("defs#f3CardDef")||e.insertAdjacentHTML("afterbegin",`\n \n \n \n \n \n \n \n \n \n \n \n \n `)}function Ge(e){return We((e=function(e){const t={img:!0,mini_tree:!0,link_break:!1,card_dim:{w:220,h:70,text_x:75,text_y:15,img_w:60,img_h:60,img_x:5,img_y:5}};e||(e={});for(const n in t)void 0===e[n]&&(e[n]=t[n]);return e}(e)).svg,e.card_dim),function(t){const n="M"===t.data.data.gender?"card-male":"F"===t.data.data.gender?"card-female":"card-genderless",r=e.card_dim,s=i.create("svg:g").attr("class",`card ${n}`).attr("transform",`translate(${[-r.w/2,-r.h/2]})`);s.append("g").attr("class","card-inner").attr("clip-path","url(#card_clip)"),this.innerHTML="",this.appendChild(s.node()),s.on("click",function(n){n.stopPropagation(),e.onCardClick.call(this,n,t)}),t.data._new_rel_data?(Ue(Ne({d:t,card_dim:r,is_new:t.data.to_add}).template,s.node(),!0),Ue(function({d:e,card_dim:t,label:n}){return{template:`\n \n \n \n ${n}\n \n \n `}}({d:t,card_dim:r,label:t.data._new_rel_data.label}).template,this.querySelector(".card-inner"),!0),i.select(this.querySelector(".card-inner")).append("g").attr("class","card-edit-icon").attr("fill","currentColor").attr("transform",`translate(-1,2)scale(${r.img_h/22})`).html(X())):(Ue(Ne({d:t,card_dim:r,is_new:t.data.to_add}).template,s.node(),!0),Ue(Ve({d:t,card_dim:r,card_display:e.card_display}).template,this.querySelector(".card-inner"),!1),e.img&&Ze(Be.cardImage(t,e),this.querySelector(".card")),e.mini_tree&&Ze(Be.miniTree(t,e),this.querySelector(".card"),!0)),e.onCardUpdate&&e.onCardUpdate.call(this,t)}}function Ke(e){return void 0===e.onCardClick&&(e.onCardClick=(t,n)=>{e.store.updateMainId(n.data.id),e.store.updateTree({})}),Ge(e)}function Ye(e,t){return new Qe(e,t)}class Qe{constructor(e,t){this.cont=e,this.active=!1,this.onClose=t,this.popup_cont=i.select(this.cont).append("div").attr("class","f3-popup").node(),this.create()}create(){const e=i.select(this.popup_cont);e.html('\n
\n ×\n
\n
\n '),e.select(".f3-popup-close").on("click",()=>{this.close()}),e.on("click",t=>{t.target==e.node()&&this.close()})}activate(e){const t=i.select(this.popup_cont).select(".f3-popup-content-inner").node();e&&t.appendChild(e),this.open()}open(){this.active=!0}close(){this.popup_cont.remove(),this.active=!1,this.onClose&&this.onClose()}}function Xe(e,t,n){const i=t.find(t=>t.id===e),r={};return function e(n,i,s,a){if(!n)return;if(r[n])return;i&&(r[n]=i);const o=t.find(e=>e.id===n),d=o.rels;if("self"===i)d.parents.forEach(t=>e(t,"parent",s-1,n)),(d.spouses||[]).forEach(t=>e(t,"spouse",s)),(d.children||[]).forEach(t=>e(t,"child",s+1));else if("parent"===i)d.parents.forEach(t=>e(t,"grandparent",s-1,n)),(d.children||[]).forEach(t=>{a&&a===t||e(t,"sibling",s+1)});else if("spouse"===i);else if("child"===i)(d.children||[]).forEach(t=>e(t,"grandchild",s+1));else if("sibling"===i)(d.children||[]).forEach(t=>e(t,"nephew",s+1));else if("grandparent"===i)a||console.error(`${i} should have prev_rel_id`),d.parents.forEach(t=>e(t,"great-grandparent",s-1,n)),(d.children||[]).forEach(t=>{a&&a===t||e(t,"uncle",s+1)});else if(i.includes("grandchild"))(d.children||[]).forEach(t=>e(t,it(i,s+1),s+1));else if(i.includes("great-grandparent"))a||console.error(`${i} should have prev_rel_id`),d.parents.forEach(t=>e(t,it(i,s-1),s-1,n)),(d.children||[]).forEach(t=>{if(a&&a===t)return;const n=nt(s+1);0===n?e(t,"granduncle",s+1):n>0?e(t,it("granduncle",s+1),s+1):console.error(`${i} should have great_count > -1`)});else if("nephew"===i)(d.children||[]).forEach(t=>e(t,"grandnephew",s+1));else if(i.includes("grandnephew"))(d.children||[]).forEach(t=>e(t,it(i,s+1),s+1));else if("uncle"===i)(d.children||[]).forEach(t=>e(t,"1st Cousin",s+1));else if("granduncle"===i)(d.children||[]).forEach(t=>e(t,"1st Cousin 1x removed",s+1));else if(i.includes("great-granduncle")){const t=s+1,n=Math.abs(t);(d.children||[]).forEach(i=>e(i,`1st Cousin ${n}x removed`,t))}else i.slice(4).startsWith("Cousin")?(d.children||[]).forEach(t=>{const n=s+1,r=Math.abs(n),a=+i[0];0===n?e(t,`${tt(a+1)} Cousin`,n):n<0?e(t,`${tt(a+1)} Cousin ${r}x removed`,n):n>0&&e(t,`${tt(a)} Cousin ${r}x removed`,n)}):console.error(`${i} not found`)}(i.id,"self",0),function(e){const n=[];Object.keys(e).forEach(r=>{const s=e[r];if(s.includes("child"))return;if("spouse"===s)return;const a=et(i.id,r,t);if(!a)return console.error(`${t.find(e=>e.id===r).data} not found in main_ancestry`);a.is_half_kin&&n.push(r)}),n.forEach(t=>{e[t]=`Half ${e[t]}`})}(r),n.show_in_law&&function(e,t){Object.keys(e).forEach(n=>{const i=e[n],r=t.find(e=>e.id===n);if("spouse"===i){const t=[];r.rels.parents.forEach(e=>(s(e).rels.children||[]).forEach(e=>t.push(e))),t.forEach(t=>{e[t]||(e[t]="sibling-in-law")})}"sibling"===i&&(r.rels.spouses||[]).forEach(t=>{e[t]||(e[t]="sibling-in-law")}),"child"===i&&(r.rels.spouses||[]).forEach(t=>{e[t]||(e[t]="child-in-law")}),"uncle"===i&&(r.rels.spouses||[]).forEach(t=>{e[t]||(e[t]="uncle-in-law")}),i.includes("Cousin")&&(r.rels.spouses||[]).forEach(t=>{e[t]||(e[t]=`${i} in-law`)})})}(r,t),function(e){Object.keys(e).forEach(n=>{const i=e[n],r=t.find(e=>e.id===n).data.gender;if(i.includes("parent")){const t="M"===r?"father":"F"===r?"mother":"parent";e[n]=e[n].replace("parent",t)}else if(i.includes("sibling")){const t="M"===r?"brother":"F"===r?"sister":"sibling";e[n]=e[n].replace("sibling",t)}else if(i.includes("child")){const t="M"===r?"son":"F"===r?"daughter":"child";e[n]=e[n].replace("child",t)}else if(i.includes("uncle")){const t="M"===r?"uncle":"F"===r?"aunt":"aunt/uncle";e[n]=e[n].replace("uncle",t)}else if(i.includes("nephew")){const t="M"===r?"nephew":"F"===r?"niece":"neice/nephew";e[n]=e[n].replace("nephew",t)}})}(r),r;function s(e){return t.find(t=>t.id===e)}}function et(e,t,n){const i=function(e){const t=[];return function e(i){const r=n.find(e=>e.id===i),s=r.rels;t.push(o(s)),s.parents.forEach(t=>e(t))}(e),t}(e);let r,s,a;return function(e){const t=n.find(t=>t.id===e);i.find(e=>e[0]===t.id||e[1]===t.id)&&(s=!0,r=e,a=!1)}(t),function(t){(n.find(t=>t.id===e).rels.spouses||[]).includes(t)&&(r=[e,t])}(t),function t(d){if(r)return;if(d===e)return s=!0,r=d,void(a=!1);const l=n.find(e=>e.id===d),c=l.rels,u=o(c),h=i.find(e=>e[0]&&u[0]&&e[0]===u[0]||e[1]&&u[1]&&e[1]===u[1]);if(h)return r=u.filter((e,t)=>e===h[t]),f=h,void(a=(p=u).some((e,t)=>e!==f[t])||f.some((e,t)=>e!==p[t]));var p,f;c.parents.forEach(e=>t(e))}(t),r?{found:r,is_ancestor:s,is_half_kin:a}:null;function o(e){return e.parents}}function tt(e){const t=["st","nd","rd"];return t[e-1]?e+t[e-1]:e+"th"}function nt(e){return Math.abs(e)-2}function it(e,t){const n=nt(t);return e.includes("great-")&&(e=e.split("great-")[1]),1===n?`great-${e}`:n>1?`${n}x-great-${e}`:(console.error(`${e} should have great_count > 1`),e)}function rt(e,t,n,r){var s;let a;const o=r[t].toLowerCase();if(o.includes("in-law")){a=t;const i=n.find(e=>e.id===a);t=o.includes("sister")||o.includes("brother")?e:null===(s=i.rels.spouses)||void 0===s?void 0:s.find(e=>r[e]&&!r[e].includes("in-law"))}const d=et(e,t,n);if(!d)return console.error(`${t} not found in main_ancestry`);const l=d.is_ancestor?d.found:d.found[0],c=n.find(e=>e.id===l),u=i.hierarchy(c,function(e){return[...e.rels.children||[]].map(e=>n.find(t=>t.id===e)).filter(e=>e)}),h=u.descendants().map(e=>e.data.id),p=g(e,h),f=g(t,h);!function e(t){t.children=(t.children||[]).filter(e=>!!p.includes(e.data.id)||!!f.includes(e.data.id)),t.children.forEach(t=>e(t)),0===t.children.length&&delete t.children}(u);const _=u.descendants().map(e=>{const t={id:e.data.id,data:JSON.parse(JSON.stringify(e.data.data)),kinship:r[e.data.id],rels:{parents:[],spouses:[],children:[]}};return e.children&&e.children.length>0&&(t.rels.children=e.children.map(e=>e.data.id)),t});return _.length>0&&!d.is_ancestor&&!d.is_half_kin&&function(e){const i=e[0];if(!d)return console.error(`${t} not found in main_ancestry`);const s=l===d.found[0]?d.found[1]:d.found[0];i.rels.spouses=[s];const a=n.find(e=>e.id===s),o={id:a.id,data:JSON.parse(JSON.stringify(a.data)),kinship:r[a.id],rels:{spouses:[i.id],children:i.rels.children,parents:[]}};e.push(o),(i.rels.children||[]).forEach(t=>{const i=n.find(e=>e.id===t);e.find(e=>e.id===t).rels.parents=[...i.rels.parents]})}(_),a&&function(e){o.includes("sister")||o.includes("brother")?function(e){var n;const i=e.find(e=>e.id===t),s=m(a);e.push({id:a,data:JSON.parse(JSON.stringify(s.data)),kinship:r[a],rels:{spouses:[],children:[],parents:[]}});const o=[];s.rels.parents.forEach(e=>(m(e).rels.children||[]).forEach(e=>o.push(e)));const d=null===(n=m(t).rels.spouses)||void 0===n?void 0:n.find(e=>o.includes(e));i.rels.spouses=[d];const l=m(d),c={id:l.id,data:JSON.parse(JSON.stringify(l.data)),kinship:r[l.id],rels:{spouses:[i.id],children:[],parents:[]}};e.push(c),s.rels.parents.forEach(t=>{const n=m(t),i="M"===n.data.gender?"Father-in-law":"F"===n.data.gender?"Mother-in-law":"Parent-in-law",r={id:n.id,data:JSON.parse(JSON.stringify(n.data)),kinship:i,rels:{spouses:[],children:[d,a],parents:[]}},o=s.rels.parents.find(e=>e!=e);o&&r.rels.parents.push(o),e.unshift(r)})}(e):function(e){const i=e.find(e=>e.id===t),s=a;i.rels.spouses=[s];const o=n.find(e=>e.id===s),d={id:o.id,data:JSON.parse(JSON.stringify(o.data)),kinship:r[o.id],rels:{spouses:[i.id],children:[],parents:[]}};e.push(d)}(e)}(_),_;function g(e,t){const i=[e];return function e(r){const s=n.find(e=>e.id===r);s.rels.parents.forEach(n=>{t.includes(n)&&(i.push(n),e(n))})}(e),i}function m(e){return n.find(t=>t.id===e)}}function st(e,t,n){const i=e.id;n.forEach(e=>{e.rels.parents.includes(i)&&(e.rels.parents[e.rels.parents.indexOf(i)]=t),e.rels.spouses&&e.rels.spouses.includes(i)&&(e.rels.spouses=e.rels.spouses.filter(e=>e!==i),e.rels.spouses.includes(t)||e.rels.spouses.push(t)),e.rels.children&&e.rels.children.includes(i)&&(e.rels.children=e.rels.children.filter(e=>e!==i),e.rels.children.includes(t)||e.rels.children.push(t))});const r=n.find(e=>e.id===t),s=n.find(e=>e.id===i);if(!s)throw new Error("New rel not found");if(!r)throw new Error("Link rel not found");if((s.rels.children||[]).forEach(e=>{r.rels.children||(r.rels.children=[]),r.rels.children.includes(e)||r.rels.children.push(e)}),(s.rels.spouses||[]).forEach(e=>{r.rels.spouses||(r.rels.spouses=[]),r.rels.spouses.includes(e)||r.rels.spouses.push(e)}),0===r.rels.parents.length)r.rels.parents=[...s.rels.parents];else{const e=r.rels.parents.find(e=>{var t;return"M"===(null===(t=n.find(t=>t.id===e))||void 0===t?void 0:t.data.gender)}),t=r.rels.parents.find(e=>{var t;return"F"===(null===(t=n.find(t=>t.id===e))||void 0===t?void 0:t.data.gender)}),i=s.rels.parents.find(e=>{var t;return"M"===(null===(t=n.find(t=>t.id===e))||void 0===t?void 0:t.data.gender)}),a=s.rels.parents.find(e=>{var t;return"F"===(null===(t=n.find(t=>t.id===e))||void 0===t?void 0:t.data.gender)});i&&(e?(console.error("link rel already has father"),r.rels.parents[r.rels.parents.indexOf(e)]=i):r.rels.parents.push(i)),a&&(t?(console.error("link rel already has mother"),r.rels.parents[r.rels.parents.indexOf(t)]=a):r.rels.parents.push(a))}n.splice(n.findIndex(e=>e.id===i),1)}function at(e,t){const n=e._new_rel_data?t.find(t=>t.id===e._new_rel_data.rel_id):null,i=function(e,t){const n=[];return i(e),n;function i(e){e.rels.parents.forEach(e=>{if(e){n.push(e);const r=t.find(t=>t.id===e);if(!r)throw new Error("Parent not found");i(r)}})}}(e,t),r=s(e,t);if(e._new_rel_data&&["son","daughter"].includes(e._new_rel_data.rel_type)){if(!n)throw new Error("Rel datum not found");r.push(...s(n,t))}return t.filter(t=>t.id!==e.id&&t.id!==(null==n?void 0:n.id)&&!t._new_rel_data&&!t.to_add&&!t.unknown).filter(e=>!i.includes(e.id)).filter(e=>!r.includes(e.id)).filter(t=>!(t.rels.spouses||[]).includes(e.id));function s(e,t){const n=[];return function e(i){(i.rels.children?[...i.rels.children]:[]).forEach(i=>{n.push(i);const r=t.find(e=>e.id===i);if(!r)throw new Error("Child not found");e(r)})}(e),n}}function ot({datum:e,store:t,fields:n,postSubmitHandler:i,addRelative:r,removeRelative:s,deletePerson:a,onCancel:o,editFirst:d,link_existing_rel_config:l,onFormCreation:c,no_edit:u,onSubmit:h,onDelete:p,canEdit:f,canDelete:_}){let g=!_||_(e);let m;!f||f(e)||(u=!0,g=!1);const v={datum_id:e.id,fields:[],onSubmit:function(n){h?h(n,e,r,()=>i({})):(n.preventDefault(),r(),i({}));function r(){const i=new FormData(n.target);z(e,t.getData(),i)}},onCancel:o,onFormCreation:c,no_edit:u,gender_field:{id:"gender",type:"switch",label:"Gender",initial_value:e.data.gender,disabled:!1,options:[{value:"M",label:"Male"},{value:"F",label:"Female"}]}};if(e._new_rel_data)m=Object.assign(Object.assign({},v),{title:e._new_rel_data.label,new_rel:!0,editable:!0});else{if(!r)throw new Error("addRelative is required");if(!s)throw new Error("removeRelative is required");m=Object.assign(Object.assign({},v),{onDelete:function(){p?p(e,()=>a(),()=>i({delete:!0})):(a(),i({delete:!0}))},addRelative:()=>r.activate(e),addRelativeCancel:()=>r.onCancel(),addRelativeActive:r.is_active,removeRelative:()=>s.activate(e),removeRelativeCancel:()=>s.onCancel(),removeRelativeActive:s.is_active,editable:!1,can_delete:g})}return(e._new_rel_data||e.to_add||e.unknown)&&l&&(m.linkExistingRelative=function(e,t,n){if(!n)throw new Error("link_existing_rel_config is required");const i={title:n.title,select_placeholder:n.select_placeholder,options:at(e,t).map(e=>({value:e.id,label:n.linkRelLabel(e)})).sort((e,t)=>"string"==typeof e.label&&"string"==typeof t.label?e.label.localeCompare(t.label):e.label{"rel_reference"===n.type?function(n){n.getRelLabel||console.error("getRelLabel is not set");"spouse"===n.rel_type&&(e.rels.spouses||[]).forEach(i=>{const r=t.getDatum(i);if(!r)throw new Error("Spouse not found");const s=`${n.id}__ref__${i}`,a={id:s,type:"rel_reference",label:n.label,rel_id:i,rel_label:n.getRelLabel(r),initial_value:e.data[s],rel_type:n.rel_type};m.fields.push(a)})}(n):"select"===n.type?function(t){if(!t.options&&!t.optionCreator)return console.error("optionCreator or options is not set for field",t);const n={id:t.id,type:t.type,label:t.label,initial_value:e.data[t.id],placeholder:t.placeholder,options:t.options||t.optionCreator(e)};m.fields.push(n)}(n):m.fields.push({id:n.id,type:n.type,label:n.label,initial_value:e.data[n.id]})}),m;function y(e){const t=e.target.value;i({link_rel_id:t})}}class dt{constructor(e,t,n){return this.store=e,this.onActivate=t,this.cancelCallback=n,this.datum=null,this.onChange=null,this.onCancel=null,this.is_active=!1,this.addRelLabels=this.addRelLabelsDefault(),this}activate(e){this.is_active&&this.onCancel(),this.onActivate(),this.is_active=!0,this.store.state.one_level_rels=!0;const t=this.store;this.datum=e;let n=this.datum.data.gender;!function(e,t,n,i){let r={parent:!0,spouse:!0,child:!0};i&&(r=Object.assign(r,i(e))),e.rels.spouses||(e.rels.spouses=[]),e.rels.children||(e.rels.children=[]),r.parent&&function(){const i=e.rels.parents,r=i.find(e=>{var n;return"M"===(null===(n=t.find(t=>t.id===e))||void 0===n?void 0:n.data.gender)}),s=i.find(e=>{var n;return"F"===(null===(n=t.find(t=>t.id===e))||void 0===n?void 0:n.data.gender)});if(i.length<2&&!r){const i=d({data:{gender:"M"},rels:{children:[e.id]}});i._new_rel_data={rel_type:"father",label:n.father,rel_id:e.id},e.rels.parents.push(i.id),t.push(i)}if(i.length<2&&!s){const i=d({data:{gender:"F"},rels:{children:[e.id]}});i._new_rel_data={rel_type:"mother",label:n.mother,rel_id:e.id},e.rels.parents.push(i.id),t.push(i)}const a=t.find(t=>t.id===e.rels.parents[0]),o=t.find(t=>t.id===e.rels.parents[1]);a.rels.spouses||(a.rels.spouses=[]),o.rels.spouses||(o.rels.spouses=[]),a.rels.spouses.includes(o.id)||a.rels.spouses.push(o.id),o.rels.spouses.includes(a.id)||o.rels.spouses.push(a.id),a.rels.children||(a.rels.children=[]),o.rels.children||(o.rels.children=[]),a.rels.children.includes(e.id)||a.rels.children.push(e.id),o.rels.children.includes(e.id)||o.rels.children.push(e.id)}(),r.spouse&&(function(){if(e.rels.spouses||(e.rels.spouses=[]),e.rels.children){let i;e.rels.children.forEach(r=>{const s=t.find(e=>e.id===r);if(1===s.rels.parents.length){const r="M"===t.find(e=>e.id===s.rels.parents[0]).data.gender?"F":"M";i||(i=d({data:{gender:r},rels:{spouses:[e.id]}})),i._new_rel_data={rel_type:"spouse",label:n.spouse,rel_id:e.id},i.rels.children.push(s.id),e.rels.spouses.push(i.id),s.rels.parents.push(i.id),t.push(i)}})}}(),function(){e.rels.spouses||(e.rels.spouses=[]);const i=d({data:{gender:"M"===e.data.gender?"F":"M"},rels:{spouses:[e.id]}});i._new_rel_data={rel_type:"spouse",label:n.spouse,rel_id:e.id},e.rels.spouses.push(i.id),t.push(i)}()),r.child&&(e.rels.children||(e.rels.children=[]),e.rels.spouses||(e.rels.spouses=[]),e.rels.spouses.forEach(i=>{const r=t.find(e=>e.id===i);r.rels.children||(r.rels.children=[]);const s=d({data:{gender:"M"},rels:{parents:[e.id,r.id]}});s._new_rel_data={rel_type:"son",label:n.son,other_parent_id:r.id,rel_id:e.id},r.rels.children.push(s.id),e.rels.children.push(s.id),t.push(s);const a=d({data:{gender:"F"},rels:{parents:[e.id,r.id]}});a._new_rel_data={rel_type:"daughter",label:n.daughter,other_parent_id:r.id,rel_id:e.id},r.rels.children.push(a.id),e.rels.children.push(a.id),t.push(a)}))}(e,this.getStoreData(),this.addRelLabels,this.canAdd),t.updateTree({}),this.onChange=function(i,r){(null==i?void 0:i._new_rel_data)?(null==r?void 0:r.link_rel_id)?st(i,r.link_rel_id,t.getData()):delete i._new_rel_data:i.id===e.id?i.data.gender!==n&&(n=i.data.gender,t.getData().forEach(e=>{const t=e._new_rel_data;t&&"spouse"===t.rel_type&&(e.data.gender="M"===e.data.gender?"F":"M")})):console.error("Something went wrong")},this.onCancel=()=>function(e){if(!e.is_active)return;e.is_active=!1,e.store.state.one_level_rels=!1,e.cleanUp(),e.cancelCallback(e.datum),e.datum=null,e.onChange=null,e.onCancel=null}(this)}setAddRelLabels(e){if("object"==typeof e){for(const t in e){const n=t;this.addRelLabels[n]=e[n]}return this}console.error("add_rel_labels must be an object")}setCanAdd(e){return this.canAdd=e,this}addRelLabelsDefault(){return{father:"Add Father",mother:"Add Mother",spouse:"Add Spouse",son:"Add Son",daughter:"Add Daughter"}}getStoreData(){return this.store.getData()}cleanUp(e){return e||(e=this.store.getData()),function(e){for(let t=e.length-1;t>=0;t--){const n=e[t];n._new_rel_data&&(e.forEach(e=>{e.rels.parents.includes(n.id)&&e.rels.parents.splice(e.rels.parents.indexOf(n.id),1),e.rels.children&&e.rels.children.includes(n.id)&&e.rels.children.splice(e.rels.children.indexOf(n.id),1),e.rels.spouses&&e.rels.spouses.includes(n.id)&&e.rels.spouses.splice(e.rels.spouses.indexOf(n.id),1)}),e.splice(t,1))}}(e),e}}class lt{constructor(e,t,n,i){return this.store=e,this.onActivate=t,this.cancelCallback=n,this.modal=i,this.datum=null,this.onChange=null,this.onCancel=null,this.is_active=!1,this}activate(e){this.is_active&&this.onCancel(),this.onActivate(),this.is_active=!0,this.store.state.one_level_rels=!0;const t=this.store;t.updateTree({}),this.datum=e,this.onChange=function(n,r){const s=function(t){if(t.is_ancestry){if(e.rels.parents.includes(t.data.id))return"parent"}else if(t.spouse){if(!e.rels.spouses)throw new Error("Spouses not found");if(e.rels.spouses.includes(t.data.id))return"spouse"}else{if(!e.rels.children)throw new Error("Children not found");if(e.rels.children.includes(t.data.id))return"children"}return null}(n),a=e.rels;"parent"===s?function(){const i=n.data.id,s=t.getDatum(i);if(!s)throw new Error("Parent not found");if(!s.rels.children)throw new Error("Parent has no children");s.rels.children=s.rels.children.filter(t=>t!==e.id),a.parents=a.parents.filter(e=>e!==i),r()}.call(this):"spouse"===s?function(){const s=n.data;o()?d.call(this):l.call(this,!0);function o(){return(s.rels.children||[]).some(e=>{const n=t.getDatum(e);if(!n)throw new Error("Child not found");return!!n.rels.parents.includes(s.id)})}function d(){const t="M"===e.data.gender?"f3-male-bg":"F"===e.data.gender?"f3-female-bg":null,n="M"===s.data.gender?"f3-male-bg":"F"===s.data.gender?"f3-female-bg":null,r=i.create("div").html(`\n

You are removing a spouse relationship. Since there are shared children, please choose which parent should keep them in the family tree.

\n
\n \n \n
\n `);r.selectAll('[data-option="assign-to-current"]').on("click",()=>{l(!0),this.modal.close()}),r.selectAll('[data-option="assign-to-spouse"]').on("click",()=>{l(!1),this.modal.close()}),this.modal.activate(r.node())}function l(i){n.data.rels.spouses=n.data.rels.spouses.filter(t=>t!==e.id),a.spouses=a.spouses.filter(e=>e!==n.data.id);const s=i?e:n.data,o=i?n.data:e;(a.children||[]).forEach(e=>{const n=t.getDatum(e);if(!n)throw new Error("Child not found");n.rels.parents.includes(o.id)&&(n.rels.parents=n.rels.parents.filter(e=>e!==o.id))}),o.rels.children&&(o.rels.children=o.rels.children.filter(e=>!(s.rels.children||[]).includes(e))),r()}}.call(this):"children"===s&&function(){if(!a.children)throw new Error("Children not found");a.children=a.children.filter(e=>e!==n.data.id),n.data.rels.parents=n.data.rels.parents.filter(t=>t!==e.id),r()}.call(this)}.bind(this),this.onCancel=function(){if(!this.is_active)return;if(this.is_active=!1,this.store.state.one_level_rels=!1,!this.datum)throw new Error("Datum not found");this.cancelCallback(this.datum),this.datum=null,this.onChange=null,this.onCancel=null}.bind(this)}}class ct{constructor(e){this.cont=e,this.active=!1,this.onClose=null,this.modal_cont=i.select(this.cont).append("div").attr("class","f3-modal").node(),i.select(this.modal_cont).style("display","none"),this.create()}create(){const e=i.select(this.modal_cont);e.html('\n
\n ×\n
\n
\n
\n '),e.select(".f3-modal-close").on("click",()=>{this.close()}),e.on("click",t=>{t.target==e.node()&&this.close()})}activate(e,{boolean:t,onAccept:n,onCancel:r}={}){this.reset();const s=i.select(this.modal_cont).select(".f3-modal-content-inner").node();if("string"==typeof e?s.innerHTML=e:s.appendChild(e),t){if(!n)throw new Error("onAccept is required");if(!r)throw new Error("onCancel is required");i.select(this.modal_cont).select(".f3-modal-content-bottom").html('\n \n \n '),i.select(this.modal_cont).select(".f3-modal-accept").on("click",()=>{n(),this.reset(),this.close()}),i.select(this.modal_cont).select(".f3-modal-cancel").on("click",()=>{this.close()}),this.onClose=r}this.open()}reset(){this.onClose=null,i.select(this.modal_cont).select(".f3-modal-content-inner").html(""),i.select(this.modal_cont).select(".f3-modal-content-bottom").html("")}open(){this.modal_cont.style.display="block",this.active=!0}close(){this.modal_cont.style.display="none",this.active=!1,this.onClose&&this.onClose()}}class ut{constructor(e,t){return this.cont=e,this.store=t,this.fields=[{type:"text",label:"first name",id:"first name"},{type:"text",label:"last name",id:"last name"},{type:"text",label:"birthday",id:"birthday"},{type:"text",label:"avatar",id:"avatar"}],this.is_fixed=!0,this.no_edit=!1,this.onChange=null,this.editFirst=!1,this.postSubmit=null,this.onFormCreation=null,this.createFormEdit=null,this.createFormNew=null,this.formCont=this.getFormContDefault(),this.modal=this.setupModal(),this.addRelativeInstance=this.setupAddRelative(),this.removeRelativeInstance=this.setupRemoveRelative(),this.history=this.createHistory(),this}open(e){e.rels||(e=e.data),this.addRelativeInstance.is_active?function(t){e._new_rel_data?t.cardEditForm(e):(t.addRelativeInstance.onCancel(),t.cardEditForm(e),t.store.updateMainId(e.id),t.store.updateTree({}))}(this):this.removeRelativeInstance.is_active?function(t,n){if(!n)throw new Error("Tree datum not found");if(!t.removeRelativeInstance.datum)throw new Error("Remove relative datum not found");if(!t.removeRelativeInstance.onCancel)throw new Error("Remove relative onCancel not found");if(!t.removeRelativeInstance.onChange)throw new Error("Remove relative onChange not found");if(e.id===t.removeRelativeInstance.datum.id)t.removeRelativeInstance.onCancel(),t.cardEditForm(e);else{function i(){t.removeRelativeInstance.onCancel(),t.updateHistory(),t.store.updateTree({})}t.removeRelativeInstance.onChange(n,i.bind(t))}}(this,this.store.getTreeDatum(e.id)):this.cardEditForm(e)}setupAddRelative(){return((e,t,n)=>new dt(e,t,n))(this.store,()=>function(e){e.removeRelativeInstance.is_active&&e.removeRelativeInstance.onCancel()}(this),e=>function(e,t){e.store.updateMainId(t.id),e.store.updateTree({}),e.openFormWithId(t.id)}(this,e))}setupRemoveRelative(){return((e,t,n,i)=>new lt(e,t,n,i))(this.store,function(){this.addRelativeInstance.is_active&&this.addRelativeInstance.onCancel();e(this.cont,!0)}.bind(this),function(t){e(this.cont,!1),this.store.updateMainId(t.id),this.store.updateTree({}),this.openFormWithId(t.id)}.bind(this),this.modal);function e(e,t){i.select(e).select("#f3Canvas").classed("f3-remove-relative-active",t)}}createHistory(){const e=Pe(this.store,this._getStoreDataCopy.bind(this),function(){var e;console.log("historyUpdateTree"),this.addRelativeInstance.is_active&&this.addRelativeInstance.onCancel();this.removeRelativeInstance.is_active&&this.removeRelativeInstance.onCancel();this.store.updateTree({initial:!1}),this.history.controls.updateButtons(),this.openFormWithId(null===(e=this.store.getMainDatum())||void 0===e?void 0:e.id),this.onChange&&this.onChange()}.bind(this)),t=this.cont.querySelector(".f3-nav-cont");if(!t)throw new Error("Nav cont not found");const n=qe(t,e);return e.changed(),n.updateButtons(),Object.assign(Object.assign({},e),{controls:n})}openWithoutRelCancel(e){this.cardEditForm(e)}getFormContDefault(){let e=i.select(this.cont).select("div.f3-form-cont").node();return e||(e=i.select(this.cont).append("div").classed("f3-form-cont",!0).node()),{el:e,populate(t){e.innerHTML="",e.appendChild(t)},open(){i.select(e).classed("opened",!0)},close(){i.select(e).classed("opened",!1).html("")}}}setFormCont(e){return this.formCont=e,this}cardEditForm(e){const t={},n=null==e?void 0:e._new_rel_data;n?t.onCancel=()=>this.addRelativeInstance.onCancel():(t.addRelative=this.addRelativeInstance,t.removeRelative=this.removeRelativeInstance,t.deletePerson=()=>{Z(e,this.store.getData()),this.openFormWithId(this.store.getLastAvailableMainDatum().id),this.store.updateTree({})});const i=ot(Object.assign({store:this.store,datum:e,postSubmitHandler:t=>function(t,n){if(t.addRelativeInstance.is_active){t.addRelativeInstance.onChange(e,n),t.postSubmit&&t.postSubmit(e,t.store.getData());const i=t.addRelativeInstance.datum;if(!i)throw new Error("Active datum not found");t.store.updateMainId(i.id),t.openWithoutRelCancel(i)}else(e.to_add||e.unknown)&&(null==n?void 0:n.link_rel_id)?(st(e,n.link_rel_id,t.store.getData()),t.store.updateMainId(n.link_rel_id),t.openFormWithId(n.link_rel_id)):(null==n?void 0:n.delete)||(t.postSubmit&&t.postSubmit(e,t.store.getData()),t.openFormWithId(e.id));t.is_fixed||t.closeForm();t.store.updateTree({}),t.updateHistory()}(this,t),fields:this.fields,onCancel:()=>{},editFirst:this.editFirst,no_edit:this.no_edit,link_existing_rel_config:this.link_existing_rel_config,onFormCreation:this.onFormCreation,onSubmit:this.onSubmit,onDelete:this.onDelete,canEdit:this.canEdit,canDelete:this.canDelete},t)),r=n?(this.createFormNew||Oe)(i,this.closeForm.bind(this)):(this.createFormEdit||Le)(i,this.closeForm.bind(this));this.formCont.populate(r),this.openForm()}openForm(){this.formCont.open()}closeForm(){this.formCont.close(),this.store.updateTree({})}fixed(){return this.is_fixed=!0,this.formCont.el&&i.select(this.formCont.el).style("position","relative"),this}absolute(){return this.is_fixed=!1,this.formCont.el&&i.select(this.formCont.el).style("position","absolute"),this}setCardClickOpen(e){return e.setOnCardClick((t,n)=>{this.isAddingRelative()||this.isRemovingRelative()?this.open(n.data):(this.open(n.data),e.onCardClickDefault(t,n))}),this}openFormWithId(e){if(e){const t=this.store.getDatum(e);if(!t)throw new Error("Datum not found");this.openWithoutRelCancel(t)}else{const e=this.store.getMainDatum();if(!e)throw new Error("Main datum not found");this.openWithoutRelCancel(e)}}setNoEdit(){return this.no_edit=!0,this}setEdit(){return this.no_edit=!1,this}setFields(e){const t=[];if(!Array.isArray(e))return console.error("fields must be an array"),this;for(const n of e)"string"==typeof n?t.push({type:"text",label:n,id:n}):"object"==typeof n?n.id?t.push(n):console.error("fields must be an array of objects with id property"):console.error("fields must be an array of strings or objects");return this.fields=t,this}setOnChange(e){return this.onChange=e,this}setCanEdit(e){return this.canEdit=e,this}setCanDelete(e){return this.canDelete=e,this}setCanAdd(e){return this.addRelativeInstance.setCanAdd(e),this}addRelative(e){return e||(e=this.store.getMainDatum()),this.addRelativeInstance.activate(e),this}setupModal(){return e=this.cont,new ct(e);var e}setEditFirst(e){return this.editFirst=e,this}isAddingRelative(){return this.addRelativeInstance.is_active}isRemovingRelative(){return this.removeRelativeInstance.is_active}setAddRelLabels(e){return this.addRelativeInstance.setAddRelLabels(e),this}setLinkExistingRelConfig(e){return this.link_existing_rel_config=e,this}setOnFormCreation(e){return this.onFormCreation=e,this}setCreateFormEdit(e){return this.createFormEdit=e,this}setCreateFormNew(e){return this.createFormNew=e,this}_getStoreDataCopy(){let e=JSON.parse(JSON.stringify(this.store.getData()));return this.addRelativeInstance.is_active&&(e=this.addRelativeInstance.cleanUp(e)),e=J(e),e}getStoreDataCopy(){return this.exportData()}exportData(){let e=this._getStoreDataCopy();return e=f(e,this.store.state.legacy_format),e}getDataJson(){return JSON.stringify(this.exportData(),null,2)}updateHistory(){this.history&&(this.history.changed(),this.history.controls.updateButtons()),this.onChange&&this.onChange()}setPostSubmit(e){return this.postSubmit=e,this}setOnSubmit(e){return this.onSubmit=e,this}setOnDelete(e){return this.onDelete=e,this}destroy(){return this.history.controls.destroy(),this.history=null,this.formCont.el&&i.select(this.formCont.el).remove(),this.addRelativeInstance.onCancel&&this.addRelativeInstance.onCancel(),this.store.updateTree({}),this}}class ht{constructor(e,t,n={}){this.cont=e,this.options=[],this.onSelect=t,this.config=n,this.autocomplete_cont=i.select(this.cont).append("div").attr("class","f3-autocomplete-cont").node(),this.create()}create(){var e;const t=this;i.select(this.autocomplete_cont).html(`\n
\n
\n \n ${$e()}\n
\n
\n
\n `);const n=i.select(this.autocomplete_cont).select(".f3-autocomplete"),r=n.select("input"),s=n.select(".f3-autocomplete-items");function a(){n.classed("active",!0);const e=r.property("value"),i=t.options.filter(t=>t.label.toLowerCase().includes(e.toLowerCase()));i.forEach(function(t){const n=t.label.toLowerCase().indexOf(e.toLowerCase());t.label_html=-1!==n?t.label.substring(0,n)+""+t.label.substring(n,n+e.length)+""+t.label.substring(n+e.length):t.label}),i.sort(function(e,t){return e.labelt.label?1:0}),d(i)}function o(){n.classed("active",!1),d([])}function d(e){s.selectAll("div.f3-autocomplete-item").data(e,e=>null==e?void 0:e.value).join("div").attr("class","f3-autocomplete-item").on("click",(e,n)=>{t.onSelect(n.value)}).html(e=>e.optionHtml?e.optionHtml(e):function(e){return`
${e.label_html}
`}(e))}n.on("focusout",()=>{setTimeout(()=>{n.node().contains(document.activeElement)||o()},200)}),r.on("focus",()=>{t.options=t.getOptions(),a()}).on("input",a).on("keydown",function(e){const n=s.selectAll("div.f3-autocomplete-item").nodes(),r=n.findIndex(e=>i.select(e).classed("f3-selected"));if("ArrowDown"===e.key){e.preventDefault();a(n,r0?r-1:n.length-1)}else if("Enter"===e.key&&-1!==r){e.preventDefault();const s=i.select(n[r]).datum();s&&t.onSelect(s.value)}function a(e,t){e.forEach(e=>i.select(e).classed("f3-selected",!1)),e[t]&&(i.select(e[t]).classed("f3-selected",!0),e[t].scrollIntoView({block:"nearest"}))}}),s.on("wheel",e=>e.stopPropagation()),n.select(".f3-autocomplete-toggle").on("click",e=>{e.stopPropagation();const t=n.classed("active");if(n.classed("active",!t),t)o();else{r.node().focus(),a()}})}setOptionsGetter(e){return this.getOptions=e,this}setOptionsGetterPerson(e,t){return this.getOptions=()=>{const i=[];return e().forEach(e=>{e.to_add||e.unknown||e._new_rel_data||i.find(t=>t.value===e.id)||i.push({label:t(e),value:e.id,optionHtml:n(e)})}),i},this;function n(t){const n=!V(t,e());return e=>`\n
\n ${we()}\n ${e.label_html}\n ${n?`${ke()}`:""}\n
\n `}}destroy(){this.autocomplete_cont.remove()}}function pt(e){const t=[];return Array.isArray(e)?e.forEach(e=>{"function"==typeof e?t.push(e):"string"==typeof e?t.push(t=>t.data[e]):Array.isArray(e)&&t.push(t=>e.map(e=>t.data[e]).join(" "))}):"function"==typeof e?t.push(e):"string"==typeof e&&t.push(t=>t.data[e]),t}function ft(e,t){return new _t(e,t)}let _t=class{constructor(e,t){return this.cont=e,this.svg=this.cont.querySelector("svg.main_svg"),this.store=t,this.card_display=[e=>`${e.data["first name"]} ${e.data["last name"]}`],this.cardImageField="avatar",this.onCardClick=this.onCardClickDefault,this.style="default",this.mini_tree=!1,this.card_dim={},this}getCard(){return Je({store:this.store,card_display:this.card_display,cardImageField:this.cardImageField,defaultPersonIcon:this.defaultPersonIcon,onCardClick:this.onCardClick,style:this.style,mini_tree:this.mini_tree,onCardUpdate:this.onCardUpdate,card_dim:this.card_dim,empty_card_label:this.store.state.single_parent_empty_card_label||"",unknown_card_label:this.store.state.unknown_card_label||"",cardInnerHtmlCreator:this.cardInnerHtmlCreator,duplicate_branch_toggle:this.store.state.duplicate_branch_toggle,onCardMouseenter:this.onCardMouseenter?this.onCardMouseenter.bind(this):void 0,onCardMouseleave:this.onCardMouseleave?this.onCardMouseleave.bind(this):void 0})}setCardDisplay(e){return this.card_display=pt(e),this}setCardImageField(e){return this.cardImageField=e,this}setDefaultPersonIcon(e){return this.defaultPersonIcon=e,this}setOnCardClick(e){return this.onCardClick=e,this}onCardClickDefault(e,t){this.store.updateMainId(t.data.id),this.store.updateTree({})}setStyle(e){return this.style=e,this}setMiniTree(e){return this.mini_tree=e,this}setOnCardUpdate(e){return this.onCardUpdate=e,this}setCardDim(e){if("object"!=typeof e)return console.error("card_dim must be an object"),this;for(let t in e){const n=e[t];if("number"!=typeof n&&"boolean"!=typeof n)return console.error(`card_dim.${t} must be a number or boolean`),this;"width"===t&&(t="w"),"height"===t&&(t="h"),"img_width"===t&&(t="img_w"),"img_height"===t&&(t="img_h"),"img_x"===t&&(t="img_x"),"img_y"===t&&(t="img_y"),this.card_dim[t]=n}return this}resetCardDim(){return this.card_dim={},this}setCardInnerHtmlCreator(e){return this.cardInnerHtmlCreator=e,this}setOnHoverPathToMain(){return this.onCardMouseenter=this.onEnterPathToMain.bind(this),this.onCardMouseleave=this.onLeavePathToMain.bind(this),this}unsetOnHoverPathToMain(){return this.onCardMouseenter=void 0,this.onCardMouseleave=void 0,this}onEnterPathToMain(e,t){this.to_transition=t.data.id;const n=this.store.getTreeMainDatum(),r=i.select(this.cont).select("div.cards_view").selectAll(".card_cont"),s=i.select(this.cont).select("svg.main_svg .links_view").selectAll(".link"),{cards_node_to_main:a,links_node_to_main:o}=function(e,t,n,i){const r=n.is_ancestry,s=t.data();let a=[],o=[];if(r){const r=[];let c=n,u=0;for(;c!==i&&u<100;){u++;const e=s.find(e=>!0===e.spouse&&(e.source===c||e.target===c));if(e){const t=l(s.filter(t=>Array.isArray(t.target)&&t.target.includes(e.source)&&t.target.includes(e.target)),i);if(!t)break;r.push(e),r.push(t),c=t.source}else{const e=l(s.filter(e=>Array.isArray(e.target)&&e.target.includes(c)),i);if(!e)break;r.push(e),c=e.source}}t.each(function(e){r.includes(e)&&a.push({link:e,node:this})});const h=d(n,r);e.each(function(e){h.includes(e)&&o.push({card:e,node:this})})}else if(n.spouse&&n.spouse.data===i.data){t.each(function(e){e.target===n&&a.push({link:e,node:this})});const r=[i,n];e.each(function(e){r.includes(e)&&o.push({card:e,node:this})})}else if(n.sibling){t.each(function(e){if(!Array.isArray(n.parents))throw new Error("datum.parents is not an array");e.source===n&&a.push({link:e,node:this}),e.source===i&&Array.isArray(e.target)&&2===e.target.length&&a.push({link:e,node:this}),n.parents.includes(e.source)&&!Array.isArray(e.target)&&n.parents.includes(e.target)&&a.push({link:e,node:this})});const r=[i,n,...n.parents||[]];e.each(function(e){r.includes(e)&&o.push({card:e,node:this})})}else{let r=[],l=n,c=0;for(;l!==i&&c<100;){c++;const e=s.find(e=>e.target===l&&Array.isArray(e.source));if(e){const t=s.find(t=>{return!0===t.spouse&&(n=[t.source,t.target],i=e.source,n.every(e=>i.some(t=>e===t)));var n,i});r.push(e),r.push(t),l=t?t.source:e.source[0]}else{const e=s.find(e=>e.target===l&&!Array.isArray(e.source));if(!e)break;r.push(e),l=e.source}}t.each(function(e){r.includes(e)&&a.push({link:e,node:this})});const u=d(i,r);e.each(function(e){u.includes(e)&&o.push({card:e,node:this})})}return{cards_node_to_main:o,links_node_to_main:a};function d(e,t){const r=t.filter(e=>e).reduce((e,t)=>(Array.isArray(t.target)?e.push(...t.target):e.push(t.target),Array.isArray(t.source)?e.push(...t.source):e.push(t.source),e),[]),s=[i,n];return function e(t){t.data.rels.children&&t.data.rels.children.forEach(t=>{const n=r.find(e=>e.data.id===t);n&&(s.push(n),e(n))})}(e),s}function l(e,t){return 0===e.length?null:1===e.length?e[0]:e.find(e=>e.source===t)}}(r,s,t,n);return a.forEach(e=>{const n=200*Math.abs(t.depth-e.card.depth);i.select(e.node.querySelector("div.card-inner")).transition().duration(0).delay(n).on("end",()=>this.to_transition===t.data.id&&i.select(e.node.querySelector("div.card-inner")).classed("f3-path-to-main",!0))}),o.forEach(e=>{const n=200*Math.abs(t.depth-e.link.depth);i.select(e.node).transition().duration(0).delay(n).on("end",()=>this.to_transition===t.data.id&&i.select(e.node).classed("f3-path-to-main",!0))}),this}onLeavePathToMain(e,t){return this.to_transition=!1,i.select(this.cont).select("div.cards_view").selectAll("div.card-inner").classed("f3-path-to-main",!1),i.select(this.cont).select("svg.main_svg .links_view").selectAll(".link").classed("f3-path-to-main",!1),this}};function gt(e,t){return new mt(e,t)}let mt=class{constructor(e,t){return this.cont=e,this.store=t,this.svg=this.cont.querySelector("svg.main_svg"),this.card_dim={w:220,h:70,text_x:75,text_y:15,img_w:60,img_h:60,img_x:5,img_y:5},this.card_display=[],this.mini_tree=!0,this.link_break=!1,this.onCardClick=this.onCardClickDefault.bind(this),this}getCard(){return Ge({store:this.store,svg:this.svg,card_dim:this.card_dim,card_display:this.card_display,mini_tree:this.mini_tree,link_break:this.link_break,onCardClick:this.onCardClick,onCardUpdate:this.onCardUpdate})}setCardDisplay(e){return this.card_display=pt(e),this}setCardDim(e){if("object"!=typeof e)return console.error("card_dim must be an object"),this;for(let t in e){const n=e[t];if("number"!=typeof n&&"boolean"!=typeof n)return console.error(`card_dim.${t} must be a number or boolean`),this;"width"===t&&(t="w"),"height"===t&&(t="h"),"img_width"===t&&(t="img_w"),"img_height"===t&&(t="img_h"),"img_x"===t&&(t="img_x"),"img_y"===t&&(t="img_y"),this.card_dim[t]=n}return function(e,t){e.querySelector("defs#f3CardDef")&&e.querySelector("defs#f3CardDef").remove(),We(e,t)}(this.svg,this.card_dim),this}setOnCardUpdate(e){return this.onCardUpdate=e,this}setMiniTree(e){return this.mini_tree=e,this}setLinkBreak(e){return this.link_break=e,this}onCardClickDefault(e,t){this.store.updateMainId(t.data.id),this.store.updateTree({})}setOnCardClick(e){return this.onCardClick=e,this}};function vt(e,t){return new yt(e,t)}class yt{constructor(e,t){this.getCard=null,this.transition_time=2e3,this.linkSpouseText=null,this.personSearch=null,this.is_card_html=!1,this.beforeUpdate=null,this.afterUpdate=null,this.cont=function(e){"string"==typeof e&&(e=document.querySelector(e));if(!e)throw new Error("cont not found");return e}(e);const{svg:n}=R(this.cont);this.svg=n,function(e){i.select(e).append("div").attr("class","f3-nav-cont")}(this.cont);const r=t&&t.length>0?t[0].id:"";return this.store=this.createStore(t,r),this.setOnUpdate(),this.editTreeInstance=null,this}createStore(e,t){return v({data:e,main_id:t,node_separation:250,level_separation:150,single_parent_empty_card:!0,is_horizontal:!1})}setOnUpdate(){this.store.setOnUpdate(e=>{this.beforeUpdate&&this.beforeUpdate(e),e=Object.assign({transition_time:this.store.state.transition_time},e||{}),this.is_card_html&&(e=Object.assign({},e||{},{cardHtml:!0})),P(this.store.getTree(),this.svg,this.getCard(),e||{}),this.linkSpouseText&&function(e,t,n){const r=[];t.data.forEach(e=>{e.coparent&&"F"===e.data.data.gender&&r.push({nodes:[e,e.coparent],id:`${e.data.id}--${e.coparent.data.id}`}),e.spouses&&e.spouses.forEach(t=>r.push({nodes:[t,e],id:`${t.data.id}--${e.data.id}`}))});const s=i.select(e).select(".links_view").selectAll("g.link-text").data(r,e=>e.id),a=s.exit(),o=s.enter().append("g").attr("class","link-text"),d=o.merge(s),l=(e,t)=>e.spouse&&"F"===e.data.data.gender?e.x-n.node_separation/2:t.spouse&&"M"===t.data.data.gender?t.x+n.node_separation/2:Math.min(e.x,t.x)+n.node_separation/2;a.each(function(e){const t=i.select(this);t.transition("text").duration(100).style("opacity",0).on("end",()=>t.remove())}),o.each(function(e){const[t,n]=e.nodes,r=i.select(this);r.attr("transform",`translate(${l(t,n)}, ${t.y-3})`).style("opacity",0),r.append("text").style("font-size","12px").style("fill","#fff").style("text-anchor","middle")}),d.each(function(e){const[r,s]=e.nodes,a=i.select(this),o=n.initial?c(t,r,n.transition_time):0;a.select("text").text(n.linkSpouseText(r,s)),a.transition("text").duration(n.transition_time).delay(o).attr("transform",`translate(${l(r,s)}, ${r.y-3})`),a.transition("text-op").duration(100).delay(o+n.transition_time).style("opacity",1)})}(this.svg,this.store.getTree(),Object.assign({},e||{},{linkSpouseText:this.linkSpouseText,node_separation:this.store.state.node_separation})),this.afterUpdate&&this.afterUpdate(e)})}updateTree(e={initial:!1}){return this.store.updateTree(e),this}updateData(e){return this.store.updateData(e),this}setCardYSpacing(e){return"number"!=typeof e?(console.error("card_y_spacing must be a number"),this):(this.store.state.level_separation=e,this)}setCardXSpacing(e){return"number"!=typeof e?(console.error("card_x_spacing must be a number"),this):(this.store.state.node_separation=e,this)}setOrientationVertical(){return this.store.state.is_horizontal=!1,this}setOrientationHorizontal(){return this.store.state.is_horizontal=!0,this}setShowSiblingsOfMain(e){return this.store.state.show_siblings_of_main=e,this}setModifyTreeHierarchy(e){return this.store.state.modifyTreeHierarchy=e,this}setPrivateCardsConfig(e){return this.store.state.private_cards_config=e,this}setLinkSpouseText(e){return this.linkSpouseText=e,this}setSingleParentEmptyCard(e,{label:t="Unknown"}={}){return this.store.state.single_parent_empty_card=e,this.store.state.single_parent_empty_card_label=t,this.editTreeInstance&&this.editTreeInstance.addRelativeInstance.is_active&&this.editTreeInstance.addRelativeInstance.onCancel(),W(this.store.getData()||[]),this}setCard(e){if(e===ft)return this.setCardHtml();if(e===gt)return this.setCardSvg();throw new Error("Card must be an instance of cardHtml or cardSvg")}setCardHtml(){const e=this.cont.querySelector("#htmlSvg");if(!e)throw new Error("htmlSvg not found");this.is_card_html=!0,this.svg.querySelector(".cards_view").innerHTML="",e.style.display="block";const t=ft(this.cont,this.store);return this.getCard=()=>t.getCard(),t}setCardSvg(){const e=this.cont.querySelector("#htmlSvg");if(!e)throw new Error("htmlSvg not found");this.is_card_html=!1,this.svg.querySelector(".cards_view").innerHTML="",e.style.display="none";const t=gt(this.cont,this.store);return this.getCard=()=>t.getCard(),t}setTransitionTime(e){return this.store.state.transition_time=e,this}setSortChildrenFunction(e){return this.store.state.sortChildrenFunction=e,this}setSortSpousesFunction(e){return this.store.state.sortSpousesFunction=e,this}setAncestryDepth(e){return this.store.state.ancestry_depth=e,this}setProgenyDepth(e){return this.store.state.progeny_depth=e,this}getMaxDepth(e){return o(e,this.store.getData())}calculateKinships(e,t={}){return Xe(e,this.store.getData(),t)}getKinshipsDataStash(e,t){return rt(e,t,this.store.getData(),this.calculateKinships(e))}setDuplicateBranchToggle(e){return this.store.state.duplicate_branch_toggle=e,this}editTree(){return this.editTreeInstance=(e=this.cont,t=this.store,new ut(e,t));var e,t}updateMain(e){let t;return t=e.id?e.id:e.data.id,this.store.updateMainId(t),this.store.updateTree({}),this}updateMainId(e){return this.store.updateMainId(e),this}getMainDatum(){return this.store.getMainDatum()}setBeforeUpdate(e){return this.beforeUpdate=e,this}setAfterUpdate(e){return this.afterUpdate=e,this}setPersonDropdown(e,{cont:t=this.cont.querySelector(".f3-nav-cont"),onSelect:n,placeholder:i="Search"}={}){return n||(n=function(e){const t=this.store.getDatum(e);if(!t)throw new Error("Datum not found");this.editTreeInstance&&this.editTreeInstance.open(t);this.updateMainId(e),this.updateTree({initial:!1})}.bind(this)),this.personSearch=function(e,t,n={}){return new ht(e,t,n)}(t,n,{placeholder:i}),this.personSearch.setOptionsGetterPerson(this.store.getData,e),this}unSetPersonSearch(){return this.personSearch.destroy(),this.personSearch=null,this}}function wt(e){return(e=e[0].toUpperCase()+e.slice(1)).includes("great-")&&(e=e.replace("great-","Great-")),e}var bt=Object.freeze({__proto__:null,Card:Ke,CardHtml:Je,CardSvg:Ge,appendElement:Ze,infoPopup:Ye,kinshipInfo:function(e,t,n){const{self_id:r,getLabel:s,title:a}=e,o=Xe(r,n,e),d=o[t];if(!d)return;let l=d;l="self"===d?"You":wt(l);const c=`\n
\n
\n ${a}\n \n ${l}\n ${Ee()}\n \n
\n
\n `,u=i.create("div").html(c).select("div").node();let h=null;return i.select(u).select(".f3-kinship-info-icon").on("click",e=>function(e,a){const d=250,l=400;let c=e.clientX-d-10,u=e.clientY-l-10;c+d>window.innerWidth&&(c=window.innerWidth-d-10);u<0&&(u=10);if(h&&h.active)return h.close(),void(h=null);h=Ye(a),i.select(h.popup_cont).style("width",`${d}px`).style("height",`${l}px`).style("left",`${c}px`).style("top",`${u}px`);const p=h.popup_cont.querySelector(".f3-popup-content-inner");h.activate(),function(e,t,n,r,s,a){i.select(s).select("#SmallChart").node()||i.select(s).append("div").attr("id","SmallChart").attr("class","f3");const o=i.select("#SmallChart");o.selectAll("*").remove();const d=rt(e,t,n,r);let l=!0;const c=o.append("div");function u(e){const n=vt("#SmallChart",e).setTransitionTime(500).setCardXSpacing(170).setCardYSpacing(70).setSingleParentEmptyCard(!1);function r(e){let n="self"===e.data.kinship?"You":e.data.kinship;return n=wt(n),l||(n=a(e.data)),`\n
\n
${n}
\n
\n `;function i(){return"self"===e.data.kinship?"card-kinship-self"+(l?"":" f3-real-label"):e.data.id===t?"card-kinship-rel":"card-kinship-default"}}function s(){c.classed("f3-kinship-labels-toggle",!0),c.append("label").text("Kinship labels").append("input").attr("type","checkbox").attr("checked",!0).on("change",e=>{l=!l,n.updateTree({initial:!1,tree_position:"inherit"})})}function o(e){const t=n.cont.querySelector("svg.main_svg");$(t).k>e&&k(t,e)}n.setCardHtml().setStyle("rect").setCardInnerHtmlCreator(e=>r(e)).setOnCardUpdate(function(e){i.select(this).select(".card").classed("card-main",!1)}).onCardClick=(e,t)=>{},n.updateTree({initial:!0}),setTimeout(()=>o(.65),100),s()}u(d)}(r,t,n,o,p,s)}(e,u)),u}});const Ct=gt,xt=ft,$t=Object.assign({},F,{setupHtmlSvg:function(e){i.select(e()).append("div").attr("class","cards_view_fake").style("display","none")},setupReactiveTreeData:T,getUniqueId:function(e){return e.unique_id}});var kt=Object.freeze({__proto__:null,CalculateTree:g,Card:Ke,CardHtml:xt,CardHtmlClass:_t,CardSvg:Ct,CardSvgClass:mt,calculateTree:m,cardHtml:ft,cardSvg:gt,createChart:vt,createStore:v,createSvg:A,elements:bt,formatData:p,formatDataForExport:f,handlers:je,htmlHandlers:$t,icons:Ie,view:P});e.CalculateTree=g,e.Card=Ke,e.CardHtml=xt,e.CardHtmlClass=_t,e.CardSvg=Ct,e.CardSvgClass=mt,e.calculateTree=m,e.cardHtml=ft,e.cardSvg=gt,e.createChart=vt,e.createStore=v,e.createSvg=A,e.default=kt,e.elements=bt,e.formatData=p,e.formatDataForExport=f,e.handlers=je,e.htmlHandlers=$t,e.icons=Ie,e.view=P,Object.defineProperty(e,"__esModule",{value:!0})}); diff --git a/templates/kin/tree.php b/templates/kin/tree.php index 792c5a9..c95a465 100644 --- a/templates/kin/tree.php +++ b/templates/kin/tree.php @@ -1,87 +1,115 @@ '曾祖辈', -2 => '祖辈', -1 => '父母辈']; - return $up[$level] ?? '上 ' . abs($level) . ' 代'; - } - $down = [1 => '子女辈', 2 => '孙辈', 3 => '曾孙辈']; - return $down[$level] ?? '下 ' . $level . ' 代'; -}; - -$levels = array_keys($gens); - -// ---- 把每一代摊成「列位(slot)」 ---- -// 一「户」占几个列位:夫妻 2(并排 + 中间一根短横)、单人 1。 -// 整棵树的列数取最宽那一代;**所有代共用这一套列**,竖线才可能对齐。 -$slotOf = []; // level => [单元key => ['start'=>起始列位, 'span'=>占几列]] -$maxSlots = 1; -foreach ($levels as $level) { - $s = 0; - foreach (($units[$level] ?? []) as $u) { - $span = $u['spouse'] !== null ? 2 : 1; - $slotOf[$level][$u['key']] = ['start' => $s, 'span' => $span]; - $s += $span; - } - // ⚠️ 至少要 1:整棵树只有「我」一个人时也不能写出 repeat(0, …)(那是无效值) - $maxSlots = max($maxSlots, max(1, $s)); } -// 列宽**固定 = 一卡之宽**(--col-w:桌面 96 / 窄屏 78),不是 1fr。 -// -// ⚠️ 为什么必须是固定值而不是 minmax(96px,1fr):列宽给 1fr 时,宽屏下列被撑到 ~195px, -// 而卡片只有 96px、在户内 flex 居中 —— 于是**卡片中心 ≠ 列中心**。 -// 连线靠 justify-self:center 落在**列**中心,就会与卡片错开(实测夫妻户偏 50px)。 -// 列宽固定成 96px 后: -// · 单列户 宽 = 96 = 卡片宽 → 卡片正好居中,中心 = 列中心; -// · 夫妻户 宽 = 96+14+96 = 两卡 + 一根短横 → 内容正好填满,两卡各自落在自己的列上。 -// 两列的间距(gap)与夫妻短横同宽,所以「户内的两个卡」与「跨户的两个卡」间距也一致。 -// 木桶效应不存在了:列宽恒定 → 卡片永远不会被压到地板以下(重叠问题从构造上消失)。 -$colStyle = 'grid-template-columns: repeat(' . (int)$maxSlots . ', var(--col-w));'; +$datum = []; +foreach ($order as $pid) { + if (!isset($people[$pid]) || !is_array($people[$pid])) { + continue; + } + $p = $people[$pid]; -// 网格行号:每代占 3 行 —— 「辈分行 / 卡片行 / 连线行」。 -// 行号写死(而不是靠自动排布)是为了让上引线、横杆、下引线精确落到同一行。 -$rowLabel = function (int $li): int { return $li * 3 + 1; }; -$rowCards = function (int $li): int { return $li * 3 + 2; }; -$rowConn = function (int $li): int { return $li * 3 + 3; }; + // 只连「名册里真的有的人」——树上出现一个查不到的人会画出空白卡 + $parents = []; + foreach (\App\Kinship::parents($graph, $pid) as $pp) { + if ($pp !== null && isset($people[$pp])) { + $parents[] = (string)$pp; + } + } + $spouses = []; + foreach (\App\Kinship::spouseIds($graph, $pid) as $sp) { + if (isset($people[$sp])) { + $spouses[] = (string)$sp; + } + } + $kids = []; + foreach (\App\Kinship::childIds($graph, $pid) as $kd) { + if (isset($people[$kd])) { + $kids[] = (string)$kd; + } + } + + // 库认 M / F / U 三种(决定卡片底色) + $g = strtolower((string)($p['gender'] ?? '')); + $gender = $g === 'm' ? 'M' : ($g === 'f' ? 'F' : 'U'); + + $datum[] = [ + 'id' => (string)$pid, + 'data' => [ + 'gender' => $gender, + 'name' => (string)($p['name'] ?? ''), + 'label' => \App\Kinship::label($graph, (int)$meId, $pid, $people), + 'avatar' => avatar_url($p['avatar'] ?? null), + 'me' => $pid === (int)$meId, + 'ph' => (int)($p['is_placeholder'] ?? 0) === 1, + ], + 'rels' => [ + 'parents' => $parents, + 'spouses' => $spouses, + 'children' => $kids, + ], + ]; +} + +// ⚠️ 名字是用户自己输入的:拼 HTML 前必须转义,否则有人把名字填成一段「能提前闭合 +// 脚本块」的标签,就能从脚本里逃出去、把整页结构打断。 +// ⚠️ 这里**刻意不写出那个闭合标签的字面量** —— 静态检查(check_inline_js.py) +// 是按原文找脚本块的,注释里写出来会被它当成真的脚本段去解析,报假错。 +$datumJson = json_encode( + $datum, + JSON_UNESCAPED_UNICODE | JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT +); + +$canChart = count($datum) > 1; ?>

我的家谱

- 同代按长幼从左到右 · 夫妻并排 · 折线表示谁是谁的父母 + 以我为中心 · 同代按长幼从左到右 · 夫妻并排 · 折线表示谁是谁的父母
- +
还没连上任何亲属关系 到「」的资料页,把父母、配偶、子女填进去, @@ -89,96 +117,97 @@ $rowConn = function (int $li): int { return $li * 3 + 3; };
-
-
-
- $level): ?> - - -
- - 户 -
- - - -
- - - 它的子女们的列位起点 - $kidStarts = []; - foreach ($units[$childLevel] as $ku) { - $pk = $ku['parent_key']; - if ($pk !== null && isset($slotOf[$level][$pk])) { - $kidStarts[$pk][] = $slotOf[$childLevel][$ku['key']]['start']; - } - } - $bars = []; - $downs = []; - foreach ($kidStarts as $pk => $starts) { - $ps = $slotOf[$level][$pk]; - $lo = min($ps['start'], min($starts)); - $hi = max($ps['start'] + $ps['span'] - 1, max($starts)); - $bars[] = ['lo' => $lo, 'hi' => $hi, 'pStart' => $ps['start'], 'pSpan' => $ps['span'], - 'who' => ($unitName[$pk]['head'] ?? '') . (($unitName[$pk]['spouse'] ?? null) !== null ? ' 夫妇' : '')]; - foreach ($starts as $cs) { - $downs[] = $cs; - } - } - sort($downs); - ?> - - -
-
- - -
- - - - -
-
-
+ +

- 「姻亲」= 因为婚姻进到这张图里的人(如我妻子的父母),不是本家的血亲。 - 折线横跨的那几户,就是它上方那一户的子女;连线从**血亲本人**头上接出。 + 「待确认」= 只填了称呼、还没对应到真实的人(虚线卡片)。 + 图里画的是**本家血脉 + 各自的配偶**;配偶的父母(如岳父母)属姻亲,不在这张图上。 + 可以直接**拖拽平移**、**滚轮缩放**;点一下卡片进对应成员页。

+ + + + \ No newline at end of file diff --git a/templates/movies/_settings_menu.php b/templates/movies/_settings_menu.php index 8174253..c03c8e2 100644 --- a/templates/movies/_settings_menu.php +++ b/templates/movies/_settings_menu.php @@ -1,38 +1,61 @@ 复制到六七个页头(改一处就得改六处、迟早漏), - * 不如让所有观影页 require 同一个片段。 + * 用户明确要求(2026-10-01): + * ① 设置按钮**去掉「设置」二字,只留图标**,图标是**三条横线**(☰); + * ② 设置按钮在**所有页面都排在最右侧**(统一位置); + * ③ 点开后的**每条条目,文字前都要加图标**; + * ④ 除设置外,其它按钮都要**有颜色**,跟设置(无色/描边)区分开。 * - * 用法(include 前 set 好): - * $setCurrent 当前页的 key('list' | 'cinemas' | 'search' | 'stats' | 'report' | 'show' | 'form') - * —— 用来高亮菜单里对应的那一项。空串表示都不高亮。 + * 实现要点: + * · 按钮本身用 .btn-icon(方形、只有图标、无文字)——与「记一场电影」等实心按钮同高; + * · 靠 CSS 的 `margin-left:auto` 把它推到动作区最右(见 app.css 的 .movie-set 一节)。 + * 这样无论各页把片段 require 在 actions 的哪个位置,它都会贴右,不必逐页调顺序。 + * · 条目图标用行内 SVG(不引外部字体/图片,避免多一次请求与 FOUC)。 + * + * 用法(require 前 set 好): + * $setCurrent 当前页 key('list'|'cinemas'|'search'|'stats'|'report'|'show'|'form'),用来高亮当前项 * $setCanWrite 是否显示「影院管理」(只有能写的人才有管理入口) - * - * ⚠️ 高亮用 `a.on`(.movie-set .menu-list a.on 已有样式)。**不做「隐藏当前页那一项」**: - * 隐藏会让菜单在不同页面长短不一,用户更难认出「这是同一套入口」。 */ $setCurrent = (string)($setCurrent ?? ''); $setCanWrite = (bool)($setCanWrite ?? false); $setOn = static function (string $key) use ($setCurrent): string { return $setCurrent === $key ? ' class="on"' : ''; }; +// 条目图标(行内 SVG,16×16,用 currentColor 跟随文字色) +$setIcon = static function (string $name): string { + $paths = [ + // 影院管理:放映机/胶片 + 'cinemas' => '', + // 高级搜索:放大镜 + 'search' => '', + // 统计:柱状图 + 'stats' => '', + // 年度报告:文档 + 'report' => '', + ]; + $d = $paths[$name] ?? ''; + if ($d === '') { + return ''; + } + return ''; +}; ?> \ No newline at end of file diff --git a/templates/movies/cinemas.php b/templates/movies/cinemas.php index db5529e..95d0f8f 100644 --- a/templates/movies/cinemas.php +++ b/templates/movies/cinemas.php @@ -18,8 +18,8 @@ $canWrite = \App\Auth::canWrite();

影院管理

共 家影院 diff --git a/templates/movies/form.php b/templates/movies/form.php index 60b954d..4a7f4fc 100644 --- a/templates/movies/form.php +++ b/templates/movies/form.php @@ -32,13 +32,12 @@ $selAttendees = array_map('intval', (array)old('attendees', $curAttendees));

- - + +
diff --git a/templates/movies/index.php b/templates/movies/index.php index 82bfd0f..2f53689 100644 --- a/templates/movies/index.php +++ b/templates/movies/index.php @@ -26,10 +26,9 @@ $fmtNum = static function ($v): string { 记一场电影 - + diff --git a/templates/movies/report.php b/templates/movies/report.php index bf635f5..4074a80 100644 --- a/templates/movies/report.php +++ b/templates/movies/report.php @@ -29,8 +29,8 @@ $fmtDate = static function (?string $d): string { - 返回观影 + diff --git a/templates/movies/search.php b/templates/movies/search.php index 7e6a69e..2ceb283 100644 --- a/templates/movies/search.php +++ b/templates/movies/search.php @@ -11,8 +11,8 @@

高级搜索

组合多个条件精确查找 diff --git a/templates/movies/show.php b/templates/movies/show.php index 0baeae3..1f7a97d 100644 --- a/templates/movies/show.php +++ b/templates/movies/show.php @@ -151,9 +151,9 @@ $commentBox = static function (int $reviewId, int $parentId, string $summary) us - - + 返回列表 + diff --git a/templates/movies/stats.php b/templates/movies/stats.php index d84664b..ebc2d6d 100644 --- a/templates/movies/stats.php +++ b/templates/movies/stats.php @@ -25,8 +25,8 @@ $wd = static function (?string $d): string {

观影统计

一家人的观影概览