domma-cms 0.36.11 → 0.36.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/admin/css/admin.css +1 -1
- package/admin/js/views/action-editor.js +1 -1
- package/admin/js/views/actions-list.js +1 -1
- package/admin/js/views/api-endpoints.js +1 -1
- package/admin/js/views/api-tokens.js +1 -1
- package/admin/js/views/blocks.js +1 -1
- package/admin/js/views/collection-entries.js +1 -1
- package/admin/js/views/collections.js +1 -1
- package/admin/js/views/components.js +4 -4
- package/admin/js/views/form-editor.js +5 -5
- package/admin/js/views/form-submissions.js +1 -1
- package/admin/js/views/forms.js +1 -1
- package/admin/js/views/media.js +1 -1
- package/admin/js/views/menu-editor.js +2 -2
- package/admin/js/views/menus.js +3 -3
- package/admin/js/views/navigation.js +2 -2
- package/admin/js/views/pages.js +4 -4
- package/admin/js/views/projects.js +1 -1
- package/admin/js/views/roles.js +3 -3
- package/admin/js/views/users.js +3 -3
- package/admin/js/views/view-editor.js +1 -1
- package/admin/js/views/views-list.js +1 -1
- package/package.json +1 -1
- package/server/routes/public.js +6 -2
- package/server/services/content.js +19 -0
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import{apiRequest as I}from"/admin/js/api.js";import{makeIconInput as fe}from"/admin/js/lib/shortcode-modal.js";const Z=[{value:"string",label:"Text (single line)"},{value:"email",label:"Email"},{value:"tel",label:"Phone"},{value:"number",label:"Number"},{value:"textarea",label:"Textarea (multi-line)"},{value:"select",label:"Dropdown (select)"},{value:"radio",label:"Radio buttons"},{value:"checkbox",label:"Single checkbox"},{value:"checkbox-group",label:"Checkbox group"},{value:"chooser",label:"Chooser (visual options)"},{value:"date",label:"Date"},{value:"time",label:"Time"},{value:"url",label:"URL"},{value:"password",label:"Password"},{value:"file",label:"File upload"},{value:"hidden",label:"Hidden field"}],W=new Set(["select","radio","checkbox-group","chooser"]);let g=[],q=null,P=null,M=null,G={};function w(e,
|
|
2
|
-
`).filter(b=>b.trim()).map(b=>{const[v,...C]=b.split(":");return{value:v.trim(),label:C.join(":").trim()||v.trim()}}))}if(
|
|
3
|
-
`).filter(y=>y.trim()).map(y=>{const[h,...b]=y.split(":");return{value:h.trim(),label:b.join(":").trim()||h.trim()}});t.cascade={sourceField:u,mapping:m,defaultOptions:f},o=!0}}return o?t:void 0}function $(){g=g.map((e,n)=>he(n))}let A=null;function ge(e,n,s){const t=e.firstElementChild;if(!t)return;const o=document.createElement("span");o.setAttribute("data-icon","menu"),o.setAttribute("data-icon-size","13"),o.title="Drag to reorder",o.style.cssText="cursor:grab;opacity:.35;flex-shrink:0;display:inline-flex;align-items:center;",t.insertBefore(o,t.firstChild),window.I?.scan?.(t),t.draggable=!0,t.addEventListener("dragstart",l=>{A=n,l.dataTransfer.effectAllowed="move",e.style.opacity="0.4"}),t.addEventListener("dragend",()=>{e.style.opacity="",document.querySelectorAll("#fields-list .fb-field-card").forEach(l=>l.classList.remove("fb-drag-over"))}),e.addEventListener("dragover",l=>{A!=null&&(l.preventDefault(),l.dataTransfer.dropEffect="move",document.querySelectorAll("#fields-list .fb-field-card").forEach(i=>i.classList.remove("fb-drag-over")),e.classList.add("fb-drag-over"))}),e.addEventListener("dragleave",()=>e.classList.remove("fb-drag-over")),e.addEventListener("drop",l=>{if(l.preventDefault(),e.classList.remove("fb-drag-over"),A==null||A===n){A=null;return}$();const[i]=g.splice(A,1);g.splice(n,0,i),A=null,L(s)})}function L(e){const n=e.find("#fields-list").get(0),s=e.find("#fields-empty-msg").get(0);if(n){if(Array.from(n.querySelectorAll(".fb-field-card")).forEach(t=>t.remove()),g.length===0){s&&(s.style.display=""),Y();return}s&&(s.style.display="none"),g.forEach((t,o)=>{const l=t.type==="page-break"?ye(t,o,e):t.type==="spacer"?ve(t,o,e):xe(t,o,e);ge(l,o,e),n.appendChild(l)}),Y()}}function ye(e,n,s){const t=document.createElement("div");t.className="fb-field-card",t.dataset.index=n,t.style.cssText="border:2px dashed var(--dm-border,#444);border-radius:8px;overflow:hidden;margin-bottom:.75rem;background:var(--dm-card-bg,rgba(255,255,255,.02));";const o=document.createElement("div");o.style.cssText="display:flex;align-items:center;gap:.6rem;padding:.6rem .8rem;cursor:pointer;user-select:none;";const l=document.createElement("span");l.textContent="\u2014 Page Break \u2014",l.style.cssText="font-size:.7rem;padding:.15rem .5rem;border-radius:999px;background:color-mix(in srgb, var(--dm-text-muted) 20%, transparent);color:var(--dm-text-muted,#888);white-space:nowrap;flex-shrink:0;font-style:italic;";const i=document.createElement("span");i.textContent=e.label||"Untitled Step",i.style.cssText="flex:1;font-weight:600;font-size:.9rem;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--dm-text-muted,#888);";const d=document.createElement("div");d.style.cssText="display:flex;gap:.25rem;flex-shrink:0;margin-left:.5rem;";const r=document.createElement("button");r.className="btn btn-xs btn-ghost",r.title="Edit step",w(r,"more-horizontal"),r.addEventListener("click",m=>{m.stopPropagation(),p.style.display=p.style.display==="none"?"":"none"}),d.appendChild(r);const a=document.createElement("button");a.className="btn btn-xs btn-ghost",a.style.cssText="line-height:1;opacity:.6;",a.title="Remove page break",w(a,"x"),a.addEventListener("click",async m=>{m.stopPropagation(),await E.confirm("Remove this page break?")&&($(),g.splice(n,1),L(s))}),d.appendChild(a),o.appendChild(l),o.appendChild(i),o.appendChild(d),o.addEventListener("click",()=>{p.style.display=p.style.display==="none"?"":"none"});const p=document.createElement("div");p.className="fb-field-body",p.style.cssText="padding:.8rem;border-top:1px dashed var(--dm-border,#444);display:none;";const c=k([S("Step Title",`fb-pb-label-${n}`,"text",e.label||"","Shown as the wizard step heading"),S("Step Description",`fb-pb-desc-${n}`,"text",e.description||"","Optional sub-heading")]),u=c.querySelector(`#fb-pb-label-${n}`);return u&&u.addEventListener("input",()=>{i.textContent=u.value||"Untitled Step"}),p.appendChild(c),t.appendChild(o),t.appendChild(p),t}function ve(e,n,s){const t=document.createElement("div");t.className="fb-field-card",t.dataset.index=n,t.style.cssText="border:1px dashed var(--dm-border,#444);border-radius:6px;margin-bottom:.5rem;background:transparent;";const o=document.createElement("div");o.style.cssText="display:flex;align-items:center;gap:.6rem;padding:.4rem .8rem;";const l=document.createElement("div");l.style.cssText="flex:1;height:1px;background:var(--dm-border,#444);";const i=document.createElement("span");i.textContent="Spacer",i.style.cssText="font-size:.7rem;color:var(--dm-text-muted,#888);white-space:nowrap;padding:0 .4rem;font-style:italic;";const d=document.createElement("div");d.style.cssText="flex:1;height:1px;background:var(--dm-border,#444);";const r=document.createElement("div");r.style.cssText="display:flex;gap:.25rem;flex-shrink:0;";const a=document.createElement("button");return a.className="btn btn-xs btn-ghost",a.style.cssText="line-height:1;opacity:.6;",a.title="Remove spacer",w(a,"x"),a.addEventListener("click",async p=>{p.stopPropagation(),$(),g.splice(n,1),L(s)}),r.appendChild(a),o.appendChild(l),o.appendChild(i),o.appendChild(d),o.appendChild(r),t.appendChild(o),t}function xe(e,n,s){const t=document.createElement("div");t.className="fb-field-card",t.dataset.index=n,t.style.cssText="border:1px solid var(--dm-border,#333);border-radius:8px;overflow:hidden;margin-bottom:.75rem;background:var(--dm-card-bg);box-shadow:var(--dm-shadow-sm);";const o=document.createElement("div");o.style.cssText="display:flex;align-items:center;gap:.6rem;padding:.65rem .85rem;background:var(--dm-card-header-bg,rgba(255,255,255,.04));cursor:pointer;user-select:none;";const l=document.createElement("span");l.textContent=ne(e.type),l.style.cssText="font-size:.7rem;padding:.15rem .45rem;border-radius:999px;background:color-mix(in srgb, var(--dm-primary) 14%, transparent);color:var(--dm-primary,#6366f1);white-space:nowrap;flex-shrink:0;";const i=document.createElement("span");i.textContent=e.label||"(unlabelled)",i.style.cssText="flex:1;font-weight:600;font-size:.9rem;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;";const d=document.createElement("div");d.style.cssText="display:flex;gap:.25rem;flex-shrink:0;margin-left:.5rem;";const r=document.createElement("button");r.className="btn btn-xs btn-ghost",r.title="Edit field",w(r,"more-horizontal"),r.addEventListener("click",c=>{c.stopPropagation(),p.style.display=p.style.display==="none"?"":"none"}),d.appendChild(r);const a=document.createElement("button");if(a.className="btn btn-xs btn-ghost",a.style.cssText="line-height:1;opacity:.6;",a.title="Remove field",w(a,"x"),a.addEventListener("click",async c=>{c.stopPropagation(),await E.confirm("Remove this field?")&&($(),g.splice(n,1),L(s))}),d.appendChild(a),o.appendChild(l),o.appendChild(i),e.required){const c=document.createElement("span");c.textContent="required",c.style.cssText="font-size:.7rem;color:var(--dm-danger,#ef4444);flex-shrink:0;",o.appendChild(c)}if(le(e.logic)){const c=document.createElement("span");w(c,"zap"),c.title="Has conditional logic",c.style.cssText="font-size:.75rem;color:var(--dm-primary,#6366f1);flex-shrink:0;",o.appendChild(c)}o.appendChild(d),o.addEventListener("click",()=>{p.style.display=p.style.display==="none"?"":"none"});const p=Ce(e,n,i);return p.style.display="none",t.appendChild(o),t.appendChild(p),t}function Ce(e,n,s){const t=document.createElement("div");t.className="fb-field-body",t.style.cssText="padding:1rem .9rem;border-top:1px solid var(--dm-border,#333);";const o=k([S("Label",`fb-label-${n}`,"text",e.label||"","Shown above the field"),S("Field Name",`fb-name-${n}`,"text",e.name||"","Used as data key")]),l=k([Ie("Type",`fb-type-${n}`,Z,e.type||"string"),de("Required",`fb-required-${n}`,e.required||!1)]),i=k([S("Placeholder",`fb-placeholder-${n}`,"text",e.placeholder||"","Hint text inside the field")]),d=k([S("Helper Text",`fb-helper-${n}`,"text",e.helper||"","Shown below the field"),S("Tooltip",`fb-tooltip-${n}`,"text",e.tooltip||"","Shown on hover via a help icon next to the label")]),r=e.formConfig?.span,a=k([S("Column Span",`fb-span-${n}`,"number",r&&r!=="full"?String(r):"1","Columns to span (grid only)"),de("Full Width",`fb-fullwidth-${n}`,r==="full")]);a.classList.add("fb-grid-row"),a.style.display=document.getElementById("setting-layout")?.value==="grid"?"flex":"none",t.appendChild(o),t.appendChild(l),t.appendChild(i),t.appendChild(d),t.appendChild(a);const p=t.querySelector(`#fb-label-${n}`),c=t.querySelector(`#fb-name-${n}`);p&&p.addEventListener("input",()=>{s&&(s.textContent=p.value||"(unlabelled)"),c&&!c.dataset.manuallyEdited&&(c.value=te(p.value))}),c&&c.addEventListener("input",()=>{c.dataset.manuallyEdited="1"});const u=t.querySelector(`#fb-type-${n}`);u&&u.addEventListener("change",()=>{const f=t.closest(".fb-field-card");if(f){const b=f.querySelector("span");b&&(b.textContent=ne(u.value))}const y=t.querySelector(".fb-field-extras");y&&y.remove();const h=se(u.value,e,n);h&&t.appendChild(h)});const m=se(e.type,e,n);return m&&t.appendChild(m),t.appendChild(Ne(e,n)),t}const Ee=[{value:"equals",label:"equals"},{value:"not_equals",label:"does not equal"},{value:"contains",label:"contains"},{value:"not_contains",label:"does not contain"},{value:"starts_with",label:"starts with"},{value:"ends_with",label:"ends with"},{value:"greater_than",label:"is greater than"},{value:"less_than",label:"is less than"},{value:"is_empty",label:"is empty"},{value:"is_not_empty",label:"is not empty"},{value:"in",label:"is one of (comma sep)"},{value:"not_in",label:"is not one of (comma sep)"},{value:"matches_regex",label:"matches regex"}],oe=new Set(["is_empty","is_not_empty"]);function H(e){const n=document.createElement("p");return n.textContent=e,n.style.cssText="font-size:.72rem;font-weight:700;color:var(--dm-text-muted,#888);margin:1rem 0 .45rem;text-transform:uppercase;letter-spacing:.06em;",n}function V(e,n,s,t,o){const l=document.createElement("div");l.className="fb-logic-cond-row",l.style.cssText="display:flex;gap:.35rem;align-items:center;margin-bottom:.35rem;flex-wrap:wrap;";const i=document.createElement("span");i.textContent="When",i.style.cssText="font-size:.73rem;color:var(--dm-text-muted,#888);flex-shrink:0;";const d=document.createElement("select");d.className="form-select fb-logic-cond-field",d.style.cssText="flex:1 1 140px;min-width:140px;",n.forEach(m=>{const f=document.createElement("option");f.value=m.value,f.textContent=m.label,e&&m.value===e.field&&(f.selected=!0),d.appendChild(f)});const r=document.createElement("select");r.className="form-select fb-logic-cond-op",r.style.cssText="flex:1 1 160px;min-width:140px;",Ee.forEach(m=>{const f=document.createElement("option");f.value=m.value,f.textContent=m.label,e&&m.value===e.operator&&(f.selected=!0),r.appendChild(f)});const a=document.createElement("input");a.type="text",a.className="form-input fb-logic-cond-val",a.placeholder="value",a.style.cssText="flex:1 1 120px;min-width:100px;",a.value=e?.value||"",e&&oe.has(e.operator)&&(a.style.display="none"),r.addEventListener("change",()=>{a.style.display=oe.has(r.value)?"none":""});const p=document.createElement("span");w(p,"arrow-right"),p.style.cssText="font-size:.73rem;color:var(--dm-text-muted,#888);flex-shrink:0;";const c=document.createElement("select");c.className=`form-select ${t}`,c.style.cssText="flex:1 1 140px;min-width:120px;",s.forEach(m=>{const f=document.createElement("option");f.value=m.value,f.textContent=m.label,m.value===o&&(f.selected=!0),c.appendChild(f)});const u=document.createElement("button");return u.type="button",u.className="btn btn-xs btn-ghost",u.style.cssText="line-height:1;opacity:.6;",w(u,"x"),u.style.flexShrink="0",u.addEventListener("click",()=>l.remove()),l.appendChild(i),l.appendChild(d),l.appendChild(r),l.appendChild(a),l.appendChild(p),l.appendChild(c),l.appendChild(u),l}function we(e,n,s){const t=document.createElement("div");t.dataset.logicSection="visibility",t.appendChild(H("Visibility"));const o=document.createElement("div");o.style.cssText="display:flex;align-items:center;gap:.5rem;margin-bottom:.4rem;";const l=document.createElement("span");l.textContent="Default:",l.style.cssText="font-size:.8rem;flex-shrink:0;";const i=document.createElement("select");i.className="form-select fb-logic-vis-default",i.style.cssText="max-width:240px;",[{value:"visible",label:"Visible"},{value:"hidden",label:"Hidden"}].forEach(m=>{const f=document.createElement("option");f.value=m.value,f.textContent=m.label,m.value===(e.default||"visible")&&(f.selected=!0),i.appendChild(f)}),o.appendChild(l),o.appendChild(i),t.appendChild(o);const d=document.createElement("div");d.style.cssText="display:flex;align-items:center;gap:.5rem;margin-bottom:.4rem;";const r=document.createElement("span");r.textContent="Transition:",r.style.cssText="font-size:.8rem;flex-shrink:0;";const a=document.createElement("select");a.className="form-select fb-logic-vis-transition",a.style.cssText="max-width:240px;",[{value:"none",label:"None (instant)"},{value:"fade",label:"Fade"},{value:"slide",label:"Slide"},{value:"scale",label:"Scale"}].forEach(m=>{const f=document.createElement("option");f.value=m.value,f.textContent=m.label,m.value===(e.transition||"none")&&(f.selected=!0),a.appendChild(f)}),d.appendChild(r),d.appendChild(a),t.appendChild(d);const p=document.createElement("div");p.className="fb-logic-vis-rules";const c=s.map(m=>({value:m.name,label:m.label||m.name})),u=[{value:"visible",label:"Show"},{value:"hidden",label:"Hide"}];if((e.conditions||[]).forEach(m=>{const f=(m.when?.all||m.when?.any||[])[0],y=m.then==="hidden"?"hidden":"visible";c.length>0&&p.appendChild(V(f,c,u,"fb-logic-vis-then",y))}),t.appendChild(p),c.length>0){const m=document.createElement("button");m.type="button",m.className="btn btn-xs btn-ghost",m.style.cssText="font-size:.73rem;margin-top:.2rem;",D(m,"plus","Add visibility rule"),m.addEventListener("click",()=>p.appendChild(V(null,c,u,"fb-logic-vis-then","visible"))),t.appendChild(m)}return t}function ke(e,n,s){const t=document.createElement("div");t.dataset.logicSection="requirement",t.appendChild(H("Conditional Requirement"));const o=document.createElement("label");o.style.cssText="display:flex;align-items:center;gap:.4rem;font-size:.8rem;cursor:pointer;margin-bottom:.4rem;";const l=document.createElement("input");l.type="checkbox",l.className="fb-logic-req-default",l.checked=e.default===!0,o.appendChild(l),o.appendChild(document.createTextNode("Required by default")),t.appendChild(o);const i=document.createElement("div");i.className="fb-logic-req-rules";const d=s.map(a=>({value:a.name,label:a.label||a.name})),r=[{value:"true",label:"Make required"},{value:"false",label:"Make optional"}];if((e.conditions||[]).forEach(a=>{const p=(a.when?.all||a.when?.any||[])[0],c=a.then===!0?"true":"false";d.length>0&&i.appendChild(V(p,d,r,"fb-logic-req-then",c))}),t.appendChild(i),d.length>0){const a=document.createElement("button");a.type="button",a.className="btn btn-xs btn-ghost",a.style.cssText="font-size:.73rem;margin-top:.2rem;",D(a,"plus","Add requirement rule"),a.addEventListener("click",()=>i.appendChild(V(null,d,r,"fb-logic-req-then","true"))),t.appendChild(a)}return t}function ae(e){const n=document.createElement("div");n.className="fb-logic-val-rule",n.style.cssText="display:flex;gap:.35rem;align-items:center;margin-bottom:.35rem;flex-wrap:wrap;";const s=document.createElement("select");s.className="form-select fb-logic-val-type",s.style.cssText="flex:0 0 140px;",[{value:"regex",label:"Regex"},{value:"match",label:"Match field"}].forEach(d=>{const r=document.createElement("option");r.value=d.value,r.textContent=d.label,d.value===(e?.type||"regex")&&(r.selected=!0),s.appendChild(r)});const t=document.createElement("input");t.type="text",t.className="form-input fb-logic-val-pattern",t.placeholder=e?.type==="match"?"field name":"pattern",t.value=e?.pattern||e?.field||"",t.style.cssText="flex:3;";const o=document.createElement("input");o.type="text",o.className="form-input fb-logic-val-flags",o.placeholder="flags",o.value=e?.flags||"",o.style.cssText="flex:0 0 55px;",e?.type==="match"&&(o.style.display="none"),s.addEventListener("change",()=>{o.style.display=s.value==="match"?"none":"",t.placeholder=s.value==="match"?"field name":"pattern"});const l=document.createElement("input");l.type="text",l.className="form-input fb-logic-val-message",l.placeholder="Error message",l.value=e?.message||"",l.style.cssText="flex:4;";const i=document.createElement("button");return i.type="button",i.className="btn btn-xs btn-ghost",i.style.cssText="line-height:1;opacity:.6;",w(i,"x"),i.style.flexShrink="0",i.addEventListener("click",()=>n.remove()),n.appendChild(s),n.appendChild(t),n.appendChild(o),n.appendChild(l),n.appendChild(i),n}function Se(e,n,s){const t=document.createElement("div");t.dataset.logicSection="validation",t.appendChild(H("Custom Validation"));const o=document.createElement("div");o.className="fb-logic-val-rules",(e||[]).forEach(i=>o.appendChild(ae(i))),t.appendChild(o);const l=document.createElement("button");return l.type="button",l.className="btn btn-xs btn-ghost",l.style.cssText="font-size:.73rem;margin-top:.2rem;",D(l,"plus","Add validation rule"),l.addEventListener("click",()=>o.appendChild(ae(null))),t.appendChild(l),t}function Te(e,n,s){const t=document.createElement("div");t.dataset.logicSection="cascade",t.appendChild(H("Cascade Options")),t.lastChild.appendChild(Q("Make this field's options depend on another field. When the source field changes, the options here are replaced by the mapping entry for its value; if nothing matches, the default options below are shown."));const o=document.createElement("div");o.style.cssText="display:flex;align-items:center;gap:.5rem;margin-bottom:.5rem;";const l=document.createElement("span");l.textContent="Source field:",l.style.cssText="font-size:.8rem;flex-shrink:0;";const i=document.createElement("select");i.className="form-select fb-logic-cascade-source",i.style.cssText="flex:1;";const d=document.createElement("option");d.value="",d.textContent="\u2014 none \u2014",i.appendChild(d),s.forEach(u=>{const m=document.createElement("option");m.value=u.name,m.textContent=u.label||u.name,u.name===e.sourceField&&(m.selected=!0),i.appendChild(m)}),o.appendChild(l),o.appendChild(i),t.appendChild(o);const r=document.createElement("p");r.textContent='Mapping JSON \u2014 {"value":[{"value":"...","label":"..."}]}',r.appendChild(Q(`Each key is a value of the source field; its list becomes this field's options when that value is picked. Example: {"uk":[{"value":"london","label":"London"}]} shows London when the source is "uk".`)),r.style.cssText="font-size:.73rem;color:var(--dm-text-muted,#888);margin:.3rem 0 .2rem;";const a=document.createElement("textarea");a.className="form-input fb-logic-cascade-mapping",a.rows=4,a.style.cssText="font-family:monospace;",a.placeholder='{"uk": [{"value": "london", "label": "London"}]}',a.value=e.mapping?JSON.stringify(e.mapping,null,2):"",t.appendChild(r),t.appendChild(a);const p=document.createElement("p");p.textContent="Default options (one per line: value:Label)",p.appendChild(Q("Shown before the source field has a value, or when its value has no mapping entry.")),p.style.cssText="font-size:.73rem;color:var(--dm-text-muted,#888);margin:.3rem 0 .2rem;";const c=document.createElement("textarea");return c.className="form-input fb-logic-cascade-defaults",c.rows=3,c.style.cssText="font-family:monospace;",c.placeholder=`option1:Option 1
|
|
1
|
+
import{apiRequest as I}from"/admin/js/api.js";import{makeIconInput as fe}from"/admin/js/lib/shortcode-modal.js";const Z=[{value:"string",label:"Text (single line)"},{value:"email",label:"Email"},{value:"tel",label:"Phone"},{value:"number",label:"Number"},{value:"textarea",label:"Textarea (multi-line)"},{value:"select",label:"Dropdown (select)"},{value:"radio",label:"Radio buttons"},{value:"checkbox",label:"Single checkbox"},{value:"checkbox-group",label:"Checkbox group"},{value:"chooser",label:"Chooser (visual options)"},{value:"date",label:"Date"},{value:"time",label:"Time"},{value:"url",label:"URL"},{value:"password",label:"Password"},{value:"file",label:"File upload"},{value:"hidden",label:"Hidden field"}],W=new Set(["select","radio","checkbox-group","chooser"]);let g=[],q=null,P=null,M=null,G={};function w(e,t,s=14){e.textContent="";const n=document.createElement("span");return n.setAttribute("data-icon",t),n.setAttribute("data-icon-size",String(s)),n.style.cssText="display:inline-flex;align-items:center;vertical-align:middle;",e.appendChild(n),window.I?.scan?.(e),e}function D(e,t,s,n=14){return w(e,t,n),e.appendChild(document.createTextNode(" "+s)),e}function Q(e){const t=document.createElement("span");return t.setAttribute("data-icon","help-circle"),t.setAttribute("data-icon-size","12"),t.style.cssText="display:inline-flex;align-items:center;vertical-align:middle;margin-left:.3rem;opacity:.55;cursor:help;",t.title=e,window.I?.scan?.(t),requestAnimationFrame(()=>{try{t.parentNode&&typeof E<"u"&&E.tooltip&&(t.removeAttribute("title"),E.tooltip(t,{content:e,position:"top"}))}catch{}}),t}let _=null,ee=null;function Y(){clearTimeout(ee),ee=setTimeout(()=>{_&&_()},350)}function te(e){return e.toLowerCase().replace(/[^a-z0-9]+/g,"_").replace(/^_|_$/g,"")}function ne(e){return Z.find(t=>t.value===e)?.label||e}function le(e){return e?!!(e.visibility?.conditions?.length||e.requirement?.conditions?.length||e.validation?.length||e.cascade?.sourceField):!1}function he(e){const t={...g[e]};if(t.type==="spacer")return t;if(t.type==="page-break"){const h=document.getElementById(`fb-pb-label-${e}`),b=document.getElementById(`fb-pb-desc-${e}`);return h&&(t.label=h.value.trim()||t.label),b&&(t.description=b.value.trim()),t}const s=document.getElementById(`fb-label-${e}`),n=document.getElementById(`fb-name-${e}`),o=document.getElementById(`fb-type-${e}`),l=document.getElementById(`fb-required-${e}`),i=document.getElementById(`fb-placeholder-${e}`),d=document.getElementById(`fb-helper-${e}`),r=document.getElementById(`fb-tooltip-${e}`);if(s&&(t.label=s.value.trim()||t.label),n&&(t.name=n.value.trim()||t.name),o&&(t.type=o.value||t.type),l&&(t.required=l.checked),i&&(t.placeholder=i.value.trim()),d&&(t.helper=d.value.trim()),r&&(t.tooltip=r.value.trim()),t.type==="chooser"){const h=document.getElementById(`fb-chooser-variant-${e}`)?.value;h&&(t.variant=h);const b=document.getElementById(`fb-chooser-multiple-${e}`);b&&(t.multiple=b.checked);const v=document.getElementById(`fb-chooser-density-${e}`)?.value;v&&(t.density=v);const C=document.getElementById(`fb-chooser-columns-${e}`)?.value;C!==""&&C!==void 0&&(t.columns=parseInt(C,10)||3);const x=document.getElementById(`fb-chooser-accent-${e}`)?.value.trim();x?t.accent=x:delete t.accent;const T=document.getElementById(`fb-chooser-accent-style-${e}`)?.value;T?t.accentStyle=T:delete t.accentStyle;const z=document.getElementById(`fb-chooser-glow-${e}`);z&&(t.glow=z.checked);const A=document.getElementById(`fb-chooser-glow-colour-${e}`)?.value.trim();A?t.glowColour=A:delete t.glowColour;const N=document.getElementById(`fb-chooser-shadow-${e}`)?.value;N&&N!=="none"?t.shadow=N:delete t.shadow;const O=document.getElementById(`fb-chooser-shadow-colour-${e}`)?.value.trim();O?t.shadowColour=O:delete t.shadowColour,J(e),g[e]&&Array.isArray(g[e].options)&&(t.options=g[e].options)}else if(W.has(t.type)){const h=document.getElementById(`fb-options-${e}`);h&&(t.options=h.value.split(`
|
|
2
|
+
`).filter(b=>b.trim()).map(b=>{const[v,...C]=b.split(":");return{value:v.trim(),label:C.join(":").trim()||v.trim()}}))}if(t.type==="textarea"){const h=parseInt(document.getElementById(`fb-rows-${e}`)?.value,10);h>0&&(t.formConfig={...t.formConfig||{},rows:h})}const a=document.getElementById(`fb-span-${e}`),p=document.getElementById(`fb-fullwidth-${e}`);if(a||p){const h={...t.formConfig||{}};if(p?.checked)h.span="full";else{const b=parseInt(a?.value,10);b>1?h.span=b:delete h.span}t.formConfig=Object.keys(h).length?h:void 0}const c=document.getElementById(`fb-minlength-${e}`)?.value;c&&(t.minLength=parseInt(c,10));const u=document.getElementById(`fb-maxlength-${e}`)?.value;u&&(t.maxLength=parseInt(u,10));const m=document.getElementById(`fb-min-${e}`)?.value;m!==""&&m!==void 0&&(t.min=parseFloat(m));const f=document.getElementById(`fb-max-${e}`)?.value;f!==""&&f!==void 0&&(t.max=parseFloat(f));const y=be(e);return y?t.logic=y:delete t.logic,t}function be(e){const t=document.querySelector(`.fb-field-card[data-index="${e}"]`);if(!t)return;const s=t.querySelector(".fb-field-logic");if(!s)return;const n={};let o=!1;const l=s.querySelector('[data-logic-section="visibility"]');if(l){const a=l.querySelector(".fb-logic-vis-default"),p=l.querySelector(".fb-logic-vis-transition"),c=Array.from(l.querySelectorAll(".fb-logic-cond-row")).map(f=>{const y=f.querySelector(".fb-logic-cond-field"),h=f.querySelector(".fb-logic-cond-op"),b=f.querySelector(".fb-logic-cond-val"),v=f.querySelector(".fb-logic-vis-then");return y?.value?{when:{all:[{field:y.value,operator:h.value,value:b.value}]},then:v.value}:null}).filter(Boolean),u=a?.value||"visible",m=p?.value||"none";(u!=="visible"||c.length>0||m!=="none")&&(n.visibility={default:u,conditions:c},m!=="none"&&(n.visibility.transition=m),o=!0)}const i=s.querySelector('[data-logic-section="requirement"]');if(i){const a=i.querySelector(".fb-logic-req-default"),p=Array.from(i.querySelectorAll(".fb-logic-cond-row")).map(c=>{const u=c.querySelector(".fb-logic-cond-field"),m=c.querySelector(".fb-logic-cond-op"),f=c.querySelector(".fb-logic-cond-val"),y=c.querySelector(".fb-logic-req-then");return u?.value?{when:{all:[{field:u.value,operator:m.value,value:f.value}]},then:y.value==="true"}:null}).filter(Boolean);p.length>0&&(n.requirement={default:a?.checked===!0,conditions:p},o=!0)}const d=s.querySelector('[data-logic-section="validation"]');if(d){const a=Array.from(d.querySelectorAll(".fb-logic-val-rule")).map(p=>{const c=p.querySelector(".fb-logic-val-type"),u=p.querySelector(".fb-logic-val-pattern"),m=p.querySelector(".fb-logic-val-flags"),f=p.querySelector(".fb-logic-val-message");if(!u?.value.trim())return null;const y=c?.value||"regex",h={type:y,message:f?.value.trim()||"Invalid value."};return y==="regex"?(h.pattern=u.value.trim(),m?.value.trim()&&(h.flags=m.value.trim())):h.field=u.value.trim(),h}).filter(Boolean);a.length>0&&(n.validation=a,o=!0)}const r=s.querySelector('[data-logic-section="cascade"]');if(r){const a=r.querySelector(".fb-logic-cascade-source"),p=r.querySelector(".fb-logic-cascade-mapping"),c=r.querySelector(".fb-logic-cascade-defaults"),u=a?.value?.trim();if(u){let m={};try{m=JSON.parse(p?.value||"{}")}catch{}const f=(c?.value||"").split(`
|
|
3
|
+
`).filter(y=>y.trim()).map(y=>{const[h,...b]=y.split(":");return{value:h.trim(),label:b.join(":").trim()||h.trim()}});n.cascade={sourceField:u,mapping:m,defaultOptions:f},o=!0}}return o?n:void 0}function $(){g=g.map((e,t)=>he(t))}let B=null;function ge(e,t,s){const n=e.firstElementChild;if(!n)return;const o=document.createElement("span");o.setAttribute("data-icon","menu"),o.setAttribute("data-icon-size","13"),o.title="Drag to reorder",o.style.cssText="cursor:grab;opacity:.35;flex-shrink:0;display:inline-flex;align-items:center;",n.insertBefore(o,n.firstChild),window.I?.scan?.(n),n.draggable=!0,n.addEventListener("dragstart",l=>{B=t,l.dataTransfer.effectAllowed="move",e.style.opacity="0.4"}),n.addEventListener("dragend",()=>{e.style.opacity="",document.querySelectorAll("#fields-list .fb-field-card").forEach(l=>l.classList.remove("fb-drag-over"))}),e.addEventListener("dragover",l=>{B!=null&&(l.preventDefault(),l.dataTransfer.dropEffect="move",document.querySelectorAll("#fields-list .fb-field-card").forEach(i=>i.classList.remove("fb-drag-over")),e.classList.add("fb-drag-over"))}),e.addEventListener("dragleave",()=>e.classList.remove("fb-drag-over")),e.addEventListener("drop",l=>{if(l.preventDefault(),e.classList.remove("fb-drag-over"),B==null||B===t){B=null;return}$();const[i]=g.splice(B,1);g.splice(t,0,i),B=null,L(s)})}function L(e){const t=e.find("#fields-list").get(0),s=e.find("#fields-empty-msg").get(0);if(t){if(Array.from(t.querySelectorAll(".fb-field-card")).forEach(n=>n.remove()),g.length===0){s&&(s.style.display=""),Y();return}s&&(s.style.display="none"),g.forEach((n,o)=>{const l=n.type==="page-break"?ye(n,o,e):n.type==="spacer"?ve(n,o,e):xe(n,o,e);ge(l,o,e),t.appendChild(l)}),Y()}}function ye(e,t,s){const n=document.createElement("div");n.className="fb-field-card",n.dataset.index=t,n.style.cssText="border:2px dashed var(--dm-border,#444);border-radius:8px;overflow:hidden;margin-bottom:.75rem;background:var(--dm-card-bg,rgba(255,255,255,.02));";const o=document.createElement("div");o.style.cssText="display:flex;align-items:center;gap:.6rem;padding:.6rem .8rem;cursor:pointer;user-select:none;";const l=document.createElement("span");l.textContent="\u2014 Page Break \u2014",l.style.cssText="font-size:.7rem;padding:.15rem .5rem;border-radius:999px;background:color-mix(in srgb, var(--dm-text-muted) 20%, transparent);color:var(--dm-text-muted,#888);white-space:nowrap;flex-shrink:0;font-style:italic;";const i=document.createElement("span");i.textContent=e.label||"Untitled Step",i.style.cssText="flex:1;font-weight:600;font-size:.9rem;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--dm-text-muted,#888);";const d=document.createElement("div");d.style.cssText="display:flex;gap:.25rem;flex-shrink:0;margin-left:.5rem;";const r=document.createElement("button");r.className="btn btn-xs btn-ghost",r.title="Edit step",w(r,"more-horizontal"),r.addEventListener("click",m=>{m.stopPropagation(),p.style.display=p.style.display==="none"?"":"none"}),d.appendChild(r);const a=document.createElement("button");a.className="btn btn-xs btn-ghost",a.style.cssText="line-height:1;opacity:.6;",a.title="Remove page break",w(a,"x"),a.addEventListener("click",async m=>{m.stopPropagation(),await E.confirm("Remove this page break?")&&($(),g.splice(t,1),L(s))}),d.appendChild(a),o.appendChild(l),o.appendChild(i),o.appendChild(d),o.addEventListener("click",()=>{p.style.display=p.style.display==="none"?"":"none"});const p=document.createElement("div");p.className="fb-field-body",p.style.cssText="padding:.8rem;border-top:1px dashed var(--dm-border,#444);display:none;";const c=k([S("Step Title",`fb-pb-label-${t}`,"text",e.label||"","Shown as the wizard step heading"),S("Step Description",`fb-pb-desc-${t}`,"text",e.description||"","Optional sub-heading")]),u=c.querySelector(`#fb-pb-label-${t}`);return u&&u.addEventListener("input",()=>{i.textContent=u.value||"Untitled Step"}),p.appendChild(c),n.appendChild(o),n.appendChild(p),n}function ve(e,t,s){const n=document.createElement("div");n.className="fb-field-card",n.dataset.index=t,n.style.cssText="border:1px dashed var(--dm-border,#444);border-radius:6px;margin-bottom:.5rem;background:transparent;";const o=document.createElement("div");o.style.cssText="display:flex;align-items:center;gap:.6rem;padding:.4rem .8rem;";const l=document.createElement("div");l.style.cssText="flex:1;height:1px;background:var(--dm-border,#444);";const i=document.createElement("span");i.textContent="Spacer",i.style.cssText="font-size:.7rem;color:var(--dm-text-muted,#888);white-space:nowrap;padding:0 .4rem;font-style:italic;";const d=document.createElement("div");d.style.cssText="flex:1;height:1px;background:var(--dm-border,#444);";const r=document.createElement("div");r.style.cssText="display:flex;gap:.25rem;flex-shrink:0;";const a=document.createElement("button");return a.className="btn btn-xs btn-ghost",a.style.cssText="line-height:1;opacity:.6;",a.title="Remove spacer",w(a,"x"),a.addEventListener("click",async p=>{p.stopPropagation(),$(),g.splice(t,1),L(s)}),r.appendChild(a),o.appendChild(l),o.appendChild(i),o.appendChild(d),o.appendChild(r),n.appendChild(o),n}function xe(e,t,s){const n=document.createElement("div");n.className="fb-field-card",n.dataset.index=t,n.style.cssText="border:1px solid var(--dm-border,#333);border-radius:8px;overflow:hidden;margin-bottom:.75rem;background:var(--dm-card-bg);box-shadow:var(--dm-shadow-sm);";const o=document.createElement("div");o.style.cssText="display:flex;align-items:center;gap:.6rem;padding:.65rem .85rem;background:var(--dm-card-header-bg,rgba(255,255,255,.04));cursor:pointer;user-select:none;";const l=document.createElement("span");l.textContent=ne(e.type),l.style.cssText="font-size:.7rem;padding:.15rem .45rem;border-radius:999px;background:color-mix(in srgb, var(--dm-primary) 14%, transparent);color:var(--dm-primary,#6366f1);white-space:nowrap;flex-shrink:0;";const i=document.createElement("span");i.textContent=e.label||"(unlabelled)",i.style.cssText="flex:1;font-weight:600;font-size:.9rem;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;";const d=document.createElement("div");d.style.cssText="display:flex;gap:.25rem;flex-shrink:0;margin-left:.5rem;";const r=document.createElement("button");r.className="btn btn-xs btn-ghost",r.title="Edit field",w(r,"more-horizontal"),r.addEventListener("click",c=>{c.stopPropagation(),p.style.display=p.style.display==="none"?"":"none"}),d.appendChild(r);const a=document.createElement("button");if(a.className="btn btn-xs btn-ghost",a.style.cssText="line-height:1;opacity:.6;",a.title="Remove field",w(a,"x"),a.addEventListener("click",async c=>{c.stopPropagation(),await E.confirm("Remove this field?")&&($(),g.splice(t,1),L(s))}),d.appendChild(a),o.appendChild(l),o.appendChild(i),e.required){const c=document.createElement("span");c.textContent="required",c.style.cssText="font-size:.7rem;color:var(--dm-danger,#ef4444);flex-shrink:0;",o.appendChild(c)}if(le(e.logic)){const c=document.createElement("span");w(c,"zap"),c.title="Has conditional logic",c.style.cssText="font-size:.75rem;color:var(--dm-primary,#6366f1);flex-shrink:0;",o.appendChild(c)}o.appendChild(d),o.addEventListener("click",()=>{p.style.display=p.style.display==="none"?"":"none"});const p=Ce(e,t,i);return p.style.display="none",n.appendChild(o),n.appendChild(p),n}function Ce(e,t,s){const n=document.createElement("div");n.className="fb-field-body",n.style.cssText="padding:1rem .9rem;border-top:1px solid var(--dm-border,#333);";const o=k([S("Label",`fb-label-${t}`,"text",e.label||"","Shown above the field"),S("Field Name",`fb-name-${t}`,"text",e.name||"","Used as data key")]),l=k([Ie("Type",`fb-type-${t}`,Z,e.type||"string"),de("Required",`fb-required-${t}`,e.required||!1)]),i=k([S("Placeholder",`fb-placeholder-${t}`,"text",e.placeholder||"","Hint text inside the field")]),d=k([S("Helper Text",`fb-helper-${t}`,"text",e.helper||"","Shown below the field"),S("Tooltip",`fb-tooltip-${t}`,"text",e.tooltip||"","Shown on hover via a help icon next to the label")]),r=e.formConfig?.span,a=k([S("Column Span",`fb-span-${t}`,"number",r&&r!=="full"?String(r):"1","Columns to span (grid only)"),de("Full Width",`fb-fullwidth-${t}`,r==="full")]);a.classList.add("fb-grid-row"),a.style.display=document.getElementById("setting-layout")?.value==="grid"?"flex":"none",n.appendChild(o),n.appendChild(l),n.appendChild(i),n.appendChild(d),n.appendChild(a);const p=n.querySelector(`#fb-label-${t}`),c=n.querySelector(`#fb-name-${t}`);p&&p.addEventListener("input",()=>{s&&(s.textContent=p.value||"(unlabelled)"),c&&!c.dataset.manuallyEdited&&(c.value=te(p.value))}),c&&c.addEventListener("input",()=>{c.dataset.manuallyEdited="1"});const u=n.querySelector(`#fb-type-${t}`);u&&u.addEventListener("change",()=>{const f=n.closest(".fb-field-card");if(f){const b=f.querySelector("span");b&&(b.textContent=ne(u.value))}const y=n.querySelector(".fb-field-extras");y&&y.remove();const h=se(u.value,e,t);h&&n.appendChild(h)});const m=se(e.type,e,t);return m&&n.appendChild(m),n.appendChild(Ne(e,t)),n}const Ee=[{value:"equals",label:"equals"},{value:"not_equals",label:"does not equal"},{value:"contains",label:"contains"},{value:"not_contains",label:"does not contain"},{value:"starts_with",label:"starts with"},{value:"ends_with",label:"ends with"},{value:"greater_than",label:"is greater than"},{value:"less_than",label:"is less than"},{value:"is_empty",label:"is empty"},{value:"is_not_empty",label:"is not empty"},{value:"in",label:"is one of (comma sep)"},{value:"not_in",label:"is not one of (comma sep)"},{value:"matches_regex",label:"matches regex"}],oe=new Set(["is_empty","is_not_empty"]);function H(e){const t=document.createElement("p");return t.textContent=e,t.style.cssText="font-size:.72rem;font-weight:700;color:var(--dm-text-muted,#888);margin:1rem 0 .45rem;text-transform:uppercase;letter-spacing:.06em;",t}function V(e,t,s,n,o){const l=document.createElement("div");l.className="fb-logic-cond-row",l.style.cssText="display:flex;gap:.35rem;align-items:center;margin-bottom:.35rem;flex-wrap:wrap;";const i=document.createElement("span");i.textContent="When",i.style.cssText="font-size:.73rem;color:var(--dm-text-muted,#888);flex-shrink:0;";const d=document.createElement("select");d.className="form-select fb-logic-cond-field",d.style.cssText="flex:1 1 140px;min-width:140px;",t.forEach(m=>{const f=document.createElement("option");f.value=m.value,f.textContent=m.label,e&&m.value===e.field&&(f.selected=!0),d.appendChild(f)});const r=document.createElement("select");r.className="form-select fb-logic-cond-op",r.style.cssText="flex:1 1 160px;min-width:140px;",Ee.forEach(m=>{const f=document.createElement("option");f.value=m.value,f.textContent=m.label,e&&m.value===e.operator&&(f.selected=!0),r.appendChild(f)});const a=document.createElement("input");a.type="text",a.className="form-input fb-logic-cond-val",a.placeholder="value",a.style.cssText="flex:1 1 120px;min-width:100px;",a.value=e?.value||"",e&&oe.has(e.operator)&&(a.style.display="none"),r.addEventListener("change",()=>{a.style.display=oe.has(r.value)?"none":""});const p=document.createElement("span");w(p,"arrow-right"),p.style.cssText="font-size:.73rem;color:var(--dm-text-muted,#888);flex-shrink:0;";const c=document.createElement("select");c.className=`form-select ${n}`,c.style.cssText="flex:1 1 140px;min-width:120px;",s.forEach(m=>{const f=document.createElement("option");f.value=m.value,f.textContent=m.label,m.value===o&&(f.selected=!0),c.appendChild(f)});const u=document.createElement("button");return u.type="button",u.className="btn btn-xs btn-ghost",u.style.cssText="line-height:1;opacity:.6;",w(u,"x"),u.style.flexShrink="0",u.addEventListener("click",()=>l.remove()),l.appendChild(i),l.appendChild(d),l.appendChild(r),l.appendChild(a),l.appendChild(p),l.appendChild(c),l.appendChild(u),l}function we(e,t,s){const n=document.createElement("div");n.dataset.logicSection="visibility",n.appendChild(H("Visibility"));const o=document.createElement("div");o.style.cssText="display:flex;align-items:center;gap:.5rem;margin-bottom:.4rem;";const l=document.createElement("span");l.textContent="Default:",l.style.cssText="font-size:.8rem;flex-shrink:0;";const i=document.createElement("select");i.className="form-select fb-logic-vis-default",i.style.cssText="max-width:240px;",[{value:"visible",label:"Visible"},{value:"hidden",label:"Hidden"}].forEach(m=>{const f=document.createElement("option");f.value=m.value,f.textContent=m.label,m.value===(e.default||"visible")&&(f.selected=!0),i.appendChild(f)}),o.appendChild(l),o.appendChild(i),n.appendChild(o);const d=document.createElement("div");d.style.cssText="display:flex;align-items:center;gap:.5rem;margin-bottom:.4rem;";const r=document.createElement("span");r.textContent="Transition:",r.style.cssText="font-size:.8rem;flex-shrink:0;";const a=document.createElement("select");a.className="form-select fb-logic-vis-transition",a.style.cssText="max-width:240px;",[{value:"none",label:"None (instant)"},{value:"fade",label:"Fade"},{value:"slide",label:"Slide"},{value:"scale",label:"Scale"}].forEach(m=>{const f=document.createElement("option");f.value=m.value,f.textContent=m.label,m.value===(e.transition||"none")&&(f.selected=!0),a.appendChild(f)}),d.appendChild(r),d.appendChild(a),n.appendChild(d);const p=document.createElement("div");p.className="fb-logic-vis-rules";const c=s.map(m=>({value:m.name,label:m.label||m.name})),u=[{value:"visible",label:"Show"},{value:"hidden",label:"Hide"}];if((e.conditions||[]).forEach(m=>{const f=(m.when?.all||m.when?.any||[])[0],y=m.then==="hidden"?"hidden":"visible";c.length>0&&p.appendChild(V(f,c,u,"fb-logic-vis-then",y))}),n.appendChild(p),c.length>0){const m=document.createElement("button");m.type="button",m.className="btn btn-xs btn-ghost",m.style.cssText="font-size:.73rem;margin-top:.2rem;",D(m,"plus","Add visibility rule"),m.addEventListener("click",()=>p.appendChild(V(null,c,u,"fb-logic-vis-then","visible"))),n.appendChild(m)}return n}function ke(e,t,s){const n=document.createElement("div");n.dataset.logicSection="requirement",n.appendChild(H("Conditional Requirement"));const o=document.createElement("label");o.style.cssText="display:flex;align-items:center;gap:.4rem;font-size:.8rem;cursor:pointer;margin-bottom:.4rem;";const l=document.createElement("input");l.type="checkbox",l.className="fb-logic-req-default",l.checked=e.default===!0,o.appendChild(l),o.appendChild(document.createTextNode("Required by default")),n.appendChild(o);const i=document.createElement("div");i.className="fb-logic-req-rules";const d=s.map(a=>({value:a.name,label:a.label||a.name})),r=[{value:"true",label:"Make required"},{value:"false",label:"Make optional"}];if((e.conditions||[]).forEach(a=>{const p=(a.when?.all||a.when?.any||[])[0],c=a.then===!0?"true":"false";d.length>0&&i.appendChild(V(p,d,r,"fb-logic-req-then",c))}),n.appendChild(i),d.length>0){const a=document.createElement("button");a.type="button",a.className="btn btn-xs btn-ghost",a.style.cssText="font-size:.73rem;margin-top:.2rem;",D(a,"plus","Add requirement rule"),a.addEventListener("click",()=>i.appendChild(V(null,d,r,"fb-logic-req-then","true"))),n.appendChild(a)}return n}function ae(e){const t=document.createElement("div");t.className="fb-logic-val-rule",t.style.cssText="display:flex;gap:.35rem;align-items:center;margin-bottom:.35rem;flex-wrap:wrap;";const s=document.createElement("select");s.className="form-select fb-logic-val-type",s.style.cssText="flex:0 0 140px;",[{value:"regex",label:"Regex"},{value:"match",label:"Match field"}].forEach(d=>{const r=document.createElement("option");r.value=d.value,r.textContent=d.label,d.value===(e?.type||"regex")&&(r.selected=!0),s.appendChild(r)});const n=document.createElement("input");n.type="text",n.className="form-input fb-logic-val-pattern",n.placeholder=e?.type==="match"?"field name":"pattern",n.value=e?.pattern||e?.field||"",n.style.cssText="flex:3;";const o=document.createElement("input");o.type="text",o.className="form-input fb-logic-val-flags",o.placeholder="flags",o.value=e?.flags||"",o.style.cssText="flex:0 0 55px;",e?.type==="match"&&(o.style.display="none"),s.addEventListener("change",()=>{o.style.display=s.value==="match"?"none":"",n.placeholder=s.value==="match"?"field name":"pattern"});const l=document.createElement("input");l.type="text",l.className="form-input fb-logic-val-message",l.placeholder="Error message",l.value=e?.message||"",l.style.cssText="flex:4;";const i=document.createElement("button");return i.type="button",i.className="btn btn-xs btn-ghost",i.style.cssText="line-height:1;opacity:.6;",w(i,"x"),i.style.flexShrink="0",i.addEventListener("click",()=>t.remove()),t.appendChild(s),t.appendChild(n),t.appendChild(o),t.appendChild(l),t.appendChild(i),t}function Se(e,t,s){const n=document.createElement("div");n.dataset.logicSection="validation",n.appendChild(H("Custom Validation"));const o=document.createElement("div");o.className="fb-logic-val-rules",(e||[]).forEach(i=>o.appendChild(ae(i))),n.appendChild(o);const l=document.createElement("button");return l.type="button",l.className="btn btn-xs btn-ghost",l.style.cssText="font-size:.73rem;margin-top:.2rem;",D(l,"plus","Add validation rule"),l.addEventListener("click",()=>o.appendChild(ae(null))),n.appendChild(l),n}function Te(e,t,s){const n=document.createElement("div");n.dataset.logicSection="cascade",n.appendChild(H("Cascade Options")),n.lastChild.appendChild(Q("Make this field's options depend on another field. When the source field changes, the options here are replaced by the mapping entry for its value; if nothing matches, the default options below are shown."));const o=document.createElement("div");o.style.cssText="display:flex;align-items:center;gap:.5rem;margin-bottom:.5rem;";const l=document.createElement("span");l.textContent="Source field:",l.style.cssText="font-size:.8rem;flex-shrink:0;";const i=document.createElement("select");i.className="form-select fb-logic-cascade-source",i.style.cssText="flex:1;";const d=document.createElement("option");d.value="",d.textContent="\u2014 none \u2014",i.appendChild(d),s.forEach(u=>{const m=document.createElement("option");m.value=u.name,m.textContent=u.label||u.name,u.name===e.sourceField&&(m.selected=!0),i.appendChild(m)}),o.appendChild(l),o.appendChild(i),n.appendChild(o);const r=document.createElement("p");r.textContent='Mapping JSON \u2014 {"value":[{"value":"...","label":"..."}]}',r.appendChild(Q(`Each key is a value of the source field; its list becomes this field's options when that value is picked. Example: {"uk":[{"value":"london","label":"London"}]} shows London when the source is "uk".`)),r.style.cssText="font-size:.73rem;color:var(--dm-text-muted,#888);margin:.3rem 0 .2rem;";const a=document.createElement("textarea");a.className="form-input fb-logic-cascade-mapping",a.rows=4,a.style.cssText="font-family:monospace;",a.placeholder='{"uk": [{"value": "london", "label": "London"}]}',a.value=e.mapping?JSON.stringify(e.mapping,null,2):"",n.appendChild(r),n.appendChild(a);const p=document.createElement("p");p.textContent="Default options (one per line: value:Label)",p.appendChild(Q("Shown before the source field has a value, or when its value has no mapping entry.")),p.style.cssText="font-size:.73rem;color:var(--dm-text-muted,#888);margin:.3rem 0 .2rem;";const c=document.createElement("textarea");return c.className="form-input fb-logic-cascade-defaults",c.rows=3,c.style.cssText="font-family:monospace;",c.placeholder=`option1:Option 1
|
|
4
4
|
option2:Option 2`,c.value=(e.defaultOptions||[]).map(u=>{const m=typeof u=="string"?u:u.value??"",f=typeof u=="string"?u:u.label??m;return m===f?m:`${m}:${f}`}).join(`
|
|
5
|
-
`),t.appendChild(p),t.appendChild(c),t}function Ne(e,n){const s=e.logic||{},t=g.filter((c,u)=>u!==n&&c.type!=="page-break"&&c.type!=="spacer"),o=document.createElement("div");o.className="fb-field-logic",o.style.cssText="margin-top:.75rem;border-top:1px solid var(--dm-border,#333);padding-top:.5rem;";const l=document.createElement("div");l.style.cssText="display:flex;align-items:center;justify-content:space-between;cursor:pointer;padding:.15rem 0;";const i=document.createElement("span");i.style.cssText="font-size:.8rem;font-weight:600;color:var(--dm-text-muted,#888);",i.textContent="\u26A1 Conditional Logic";const d=le(s),r=document.createElement("button");r.type="button",r.className="btn btn-xs btn-ghost",w(r,d?"chevron-down":"chevron-right",12);const a=document.createElement("div");a.className="fb-logic-body",a.style.cssText="padding:.25rem 0 .25rem;"+(d?"":"display:none;"),l.addEventListener("click",()=>{const c=a.style.display==="none";a.style.display=c?"":"none",w(r,c?"chevron-down":"chevron-right",12)}),l.appendChild(i),l.appendChild(r),o.appendChild(l),a.appendChild(we(s.visibility||{},n,t)),a.appendChild(ke(s.requirement||{},n,t)),a.appendChild(Se(s.validation||[],n,t));const p=document.getElementById(`fb-type-${n}`)?.value||e.type;return W.has(p)&&a.appendChild(Te(s.cascade||{},n,t)),o.appendChild(a),o}function se(e,n,s){const t=document.createElement("div");return t.className="fb-field-extras",e==="chooser"?(t.appendChild(qe(n,s)),t.appendChild(Le(n.options||[],s))):(W.has(e)&&t.appendChild(Fe(n.options||[],s)),e==="textarea"&&t.appendChild(k([S("Rows",`fb-rows-${s}`,"number",n.formConfig?.rows||4,"Height of textarea")])),(e==="string"||e==="textarea")&&t.appendChild(k([S("Min Length",`fb-minlength-${s}`,"number",n.minLength||"",""),S("Max Length",`fb-maxlength-${s}`,"number",n.maxLength||"","")])),e==="number"&&t.appendChild(k([S("Min",`fb-min-${s}`,"number",n.min??"",""),S("Max",`fb-max-${s}`,"number",n.max??"","")]))),t.children.length?t:null}function ie(e){const n=document.createElement("div");n.className="fb-chooser-section",n.style.cssText="border:1px solid var(--dm-border, #e5e7eb); border-radius:6px; margin-top:.5rem; overflow:hidden;";const s=document.createElement("div");s.style.cssText="padding:.4rem .6rem; background:color-mix(in srgb, var(--dm-background, #f9fafb) 60%, transparent); font-weight:600; font-size:.82rem; border-bottom:1px solid var(--dm-border, #e5e7eb);",s.textContent=e,n.appendChild(s);const t=document.createElement("div");return t.className="fb-chooser-section-body",t.style.cssText="padding:.55rem .6rem; display:flex; flex-direction:column; gap:.45rem;",n.appendChild(t),{wrap:n,body:t}}function U(e,n,s,t,o){const l=document.createElement("div");l.style.flex="1";const i=document.createElement("label");i.htmlFor=e,i.className="form-label",i.textContent=n,i.style.fontSize=".8rem";const d=document.createElement("select");if(d.id=e,d.className="form-input",t.forEach(([r,a])=>{const p=document.createElement("option");p.value=r,p.textContent=a,String(r)===String(s)&&(p.selected=!0),d.appendChild(p)}),l.appendChild(i),l.appendChild(d),o){const r=document.createElement("p");r.className="form-hint text-muted",r.textContent=o,r.style.cssText="font-size:.72rem;margin-top:.2rem;",l.appendChild(r)}return l}function ce(e,n,s,t){const o=document.createElement("div");o.style.flex="1";const l=document.createElement("label");l.style.cssText="display:inline-flex;align-items:center;gap:.4rem;font-size:.82rem;";const i=document.createElement("input");if(i.id=e,i.type="checkbox",i.checked=!!s,l.appendChild(i),l.appendChild(document.createTextNode(" "+n)),o.appendChild(l),t){const d=document.createElement("p");d.className="form-hint text-muted",d.textContent=t,d.style.cssText="font-size:.72rem;margin-top:.2rem;",o.appendChild(d)}return o}function K(e,n,s,t,o){const l=document.createElement("div");l.style.flex="1";const i=document.createElement("label");i.htmlFor=e,i.className="form-label",i.textContent=n,i.style.fontSize=".8rem",l.appendChild(i);const d=document.createElement("div");d.style.cssText="display:flex;gap:.4rem;align-items:stretch;";const r=document.createElement("input");r.id=e,r.type="text",r.className="form-input",r.value=s||"",t&&(r.placeholder=t),r.style.flex="1";const a=document.createElement("input");a.type="color",a.title="Pick a colour",a.style.cssText="width:36px;height:auto;padding:0;border:1px solid var(--dm-border,#e5e7eb);border-radius:4px;cursor:pointer;";const p=String(r.value).match(/^#[0-9a-fA-F]{3,8}$/);if(a.value=p?r.value:"#000000",a.addEventListener("input",()=>{r.value=a.value,r.dispatchEvent(new Event("input",{bubbles:!0}))}),d.appendChild(r),d.appendChild(a),l.appendChild(d),o){const c=document.createElement("p");c.className="form-hint text-muted",c.textContent=o,c.style.cssText="font-size:.72rem;margin-top:.2rem;",l.appendChild(c)}return l}function qe(e,n){const s=document.createElement("div");s.style.cssText="margin-top:.5rem; display:flex; flex-direction:column; gap:.4rem;";const t=ie("Layout");t.body.appendChild(k([U(`fb-chooser-variant-${n}`,"Variant",e.variant||"card",[["card","Card \u2014 rich tiles"],["chip","Chip \u2014 compact pills"]],"Card supports description and grid columns; chips wrap."),ce(`fb-chooser-multiple-${n}`,"Multi-select",!!e.multiple,"Off = single (radio); on = multi (checkbox).")])),t.body.appendChild(k([U(`fb-chooser-density-${n}`,"Density",e.density||"comfortable",[["comfortable","Comfortable"],["compact","Compact"]],"Compact strips description (card variant only)."),S("Columns",`fb-chooser-columns-${n}`,"number",e.columns||3,"Card grid (1\u20136). Ignored for chips.")])),s.appendChild(t.wrap);const o=ie("Visual");return o.body.appendChild(k([K(`fb-chooser-accent-${n}`,"Accent",e.accent||"","primary / success / #ec4899","Selected highlight colour \u2014 semantic name or any CSS colour."),U(`fb-chooser-accent-style-${n}`,"Accent style",e.accentStyle||"border",[["border","Border (default)"],["solid","Solid"],["glow","Glow"],["overlay","Overlay"],["underline","Underline"]],"Visual treatment of the selected state.")])),o.body.appendChild(k([ce(`fb-chooser-glow-${n}`,"Glow on selected",!!e.glow,"Soft outer glow."),K(`fb-chooser-glow-colour-${n}`,"Glow colour",e.glowColour||"","defaults to accent","Optional; semantic name or CSS colour.")])),o.body.appendChild(k([U(`fb-chooser-shadow-${n}`,"Shadow",e.shadow||"none",[["none","None"],["sm","Small"],["md","Medium"],["lg","Large"],["xl","Extra Large"]],"Drop shadow on every option."),K(`fb-chooser-shadow-colour-${n}`,"Shadow colour",e.shadowColour||"","rgba(0,0,0,0.1)","Optional shadow tint.")])),s.appendChild(o.wrap),s}function $e(e,n,s,t){const o=document.createElement("div");o.className="fb-chooser-option-row",o.dataset.optIdx=String(s),o.style.cssText="border:1px solid var(--dm-border,#e5e7eb); border-radius:4px; padding:.5rem; background:var(--dm-background-alt, color-mix(in srgb, var(--dm-card-bg, #fff) 92%, var(--dm-text, #111))); display:flex; flex-direction:column; gap:.35rem;";const l=document.createElement("div");l.style.cssText="display:flex; gap:.4rem; align-items:center;";const i=document.createElement("div");i.style.cssText="display:flex; flex-direction:column; gap:1px;";const d=document.createElement("button");d.type="button",d.className="btn btn-xs btn-ghost",d.title="Move up",w(d,"arrow-up"),d.addEventListener("click",N=>{N.preventDefault(),re(n,s,-1,t)});const r=document.createElement("button");r.type="button",r.className="btn btn-xs btn-ghost",r.title="Move down",w(r,"arrow-down"),r.addEventListener("click",N=>{N.preventDefault(),re(n,s,1,t)}),i.appendChild(d),i.appendChild(r),l.appendChild(i);const a=document.createElement("input");a.type="text",a.className="form-input fb-chooser-opt-value",a.placeholder="value",a.value=e.value||"",a.style.flex="1",l.appendChild(a);const p=document.createElement("input");p.type="text",p.className="form-input fb-chooser-opt-label",p.placeholder="label (visible to users)",p.value=e.label||"",p.style.flex="2",l.appendChild(p);const c=document.createElement("button");c.type="button",c.className="btn btn-xs btn-ghost",c.style.cssText="line-height:1;opacity:.6;",c.title="Remove option",w(c,"x"),c.addEventListener("click",N=>{N.preventDefault(),Ae(n,s,t)}),l.appendChild(c),o.appendChild(l);const u=document.createElement("div");u.style.cssText="display:flex; gap:.4rem;";const m=fe("icon name (optional)",e.icon||"");m.input.classList.add("fb-chooser-opt-icon"),m.el.style.flex="1",u.appendChild(m.el);const f=document.createElement("input");f.type="text",f.className="form-input fb-chooser-opt-description",f.placeholder="description (card + comfortable only)",f.value=e.description||"",f.style.flex="2",u.appendChild(f),o.appendChild(u);const y=document.createElement("div");y.style.cssText="display:flex; gap:.4rem;";const h=document.createElement("input");h.type="text",h.className="form-input fb-chooser-opt-tooltip",h.placeholder="tooltip (hover hint)",h.value=e.tooltip||"",h.style.flex="2",y.appendChild(h);const b=document.createElement("input");b.type="text",b.className="form-input fb-chooser-opt-badge-text",b.placeholder="badge text",b.value=e.badge?.text||"",b.style.flex="1",y.appendChild(b);const v=document.createElement("select");v.className="form-input fb-chooser-opt-badge-type",v.style.flex="1",[["","no badge"],["primary","primary"],["success","success"],["info","info"],["warning","warning"],["danger","danger"]].forEach(([N,O])=>{const j=document.createElement("option");j.value=N,j.textContent=O,(e.badge?.type||"")===N&&(j.selected=!0),v.appendChild(j)}),y.appendChild(v),o.appendChild(y);const C=document.createElement("div");C.style.cssText="display:flex; gap:1rem; font-size:.8rem;";const x=document.createElement("label");x.style.cssText="display:inline-flex;align-items:center;gap:.3rem;";const T=document.createElement("input");T.type="checkbox",T.className="fb-chooser-opt-recommended",T.checked=!!e.recommended,x.appendChild(T),x.appendChild(document.createTextNode(" Recommended")),C.appendChild(x);const z=document.createElement("label");z.style.cssText="display:inline-flex;align-items:center;gap:.3rem;";const B=document.createElement("input");return B.type="checkbox",B.className="fb-chooser-opt-disabled",B.checked=!!e.disabled,z.appendChild(B),z.appendChild(document.createTextNode(" Disabled")),C.appendChild(z),o.appendChild(C),o}function Le(e,n){const s=document.createElement("div");s.style.cssText="margin-top:.5rem;";const t=document.createElement("div");t.style.cssText="display:flex; align-items:center; justify-content:space-between; margin-bottom:.4rem;";const o=document.createElement("p");o.textContent="Options",o.style.cssText="font-size:.85rem;font-weight:600;margin:0;",t.appendChild(o);const l=document.createElement("button");l.type="button",l.className="btn btn-xs btn-ghost",D(l,"plus","Add option"),t.appendChild(l),s.appendChild(t);const i=document.createElement("div");i.className="fb-chooser-options-list",i.id=`fb-chooser-options-${n}`,i.style.cssText="display:flex; flex-direction:column; gap:.4rem;",s.appendChild(i);const d=()=>{i.replaceChildren();const r=ze(n);(r.length?r:e||[]).forEach((p,c)=>{i.appendChild($e(p,n,c,d))})};return l.addEventListener("click",r=>{r.preventDefault(),Be(n,d)}),d(),s}function ze(e){const n=g[e];return!n||!Array.isArray(n.options)?[]:n.options}function X(e){return g[e]?(Array.isArray(g[e].options)||(g[e].options=[]),g[e].options):[]}function Be(e,n){J(e),X(e).push({value:"new",label:"New option"}),n()}function Ae(e,n,s){J(e),X(e).splice(n,1),s()}function re(e,n,s,t){J(e);const o=X(e),l=n+s;if(l<0||l>=o.length)return;const[i]=o.splice(n,1);o.splice(l,0,i),t()}function J(e){const n=document.getElementById(`fb-chooser-options-${e}`);if(!n)return;const s=n.querySelectorAll(".fb-chooser-option-row"),t=[];s.forEach(o=>{const l={value:o.querySelector(".fb-chooser-opt-value")?.value.trim()||"",label:o.querySelector(".fb-chooser-opt-label")?.value.trim()||""},i=o.querySelector(".fb-chooser-opt-icon")?.value.trim();i&&(l.icon=i);const d=o.querySelector(".fb-chooser-opt-description")?.value.trim();d&&(l.description=d);const r=o.querySelector(".fb-chooser-opt-tooltip")?.value.trim();r&&(l.tooltip=r);const a=o.querySelector(".fb-chooser-opt-badge-text")?.value.trim(),p=o.querySelector(".fb-chooser-opt-badge-type")?.value;(a||p)&&(l.badge={},a&&(l.badge.text=a),p&&(l.badge.type=p)),o.querySelector(".fb-chooser-opt-recommended")?.checked&&(l.recommended=!0),o.querySelector(".fb-chooser-opt-disabled")?.checked&&(l.disabled=!0),t.push(l)}),g[e]&&(g[e].options=t)}function k(e){const n=document.createElement("div");return n.style.cssText="display:flex;gap:.85rem;margin-bottom:.75rem;",e.forEach(s=>{s&&n.appendChild(s)}),n}function S(e,n,s,t,o){const l=document.createElement("div");l.style.flex="1";const i=document.createElement("label");i.htmlFor=n,i.className="form-label",i.textContent=e,i.style.fontSize=".8rem";const d=document.createElement("input");if(d.id=n,d.type=s||"text",d.className="form-input",d.value=t??"",l.appendChild(i),l.appendChild(d),o){const r=document.createElement("p");r.className="form-hint text-muted",r.textContent=o,r.style.cssText="font-size:.73rem;margin-top:.2rem;",l.appendChild(r)}return l}function Ie(e,n,s,t){const o=document.createElement("div");o.style.flex="1";const l=document.createElement("label");l.htmlFor=n,l.className="form-label",l.textContent=e,l.style.fontSize=".8rem";const i=document.createElement("select");return i.id=n,i.className="form-select",s.forEach(d=>{const r=document.createElement("option");r.value=d.value,r.textContent=d.label,d.value===t&&(r.selected=!0),i.appendChild(r)}),o.appendChild(l),o.appendChild(i),o}function de(e,n,s){const t=document.createElement("div");t.style.cssText="flex:0;min-width:80px;display:flex;flex-direction:column;justify-content:flex-end;";const o=document.createElement("label");o.style.cssText="display:flex;align-items:center;gap:.4rem;cursor:pointer;font-size:.8rem;white-space:nowrap;";const l=document.createElement("input");return l.id=n,l.type="checkbox",l.checked=s,o.appendChild(l),o.appendChild(document.createTextNode(e)),t.appendChild(o),t}function Fe(e,n){const s=document.createElement("div");s.style.cssText="margin-top:.4rem;";const t=document.createElement("p");t.textContent="Options (one per line: value or value:Label)",t.style.cssText="font-size:.8rem;font-weight:600;margin-bottom:.3rem;";const o=document.createElement("textarea");return o.id=`fb-options-${n}`,o.className="form-input",o.rows=4,o.placeholder=`yes:Yes
|
|
5
|
+
`),n.appendChild(p),n.appendChild(c),n}function Ne(e,t){const s=e.logic||{},n=g.filter((c,u)=>u!==t&&c.type!=="page-break"&&c.type!=="spacer"),o=document.createElement("div");o.className="fb-field-logic",o.style.cssText="margin-top:.75rem;border-top:1px solid var(--dm-border,#333);padding-top:.5rem;";const l=document.createElement("div");l.style.cssText="display:flex;align-items:center;justify-content:space-between;cursor:pointer;padding:.15rem 0;";const i=document.createElement("span");i.style.cssText="font-size:.8rem;font-weight:600;color:var(--dm-text-muted,#888);",i.textContent="\u26A1 Conditional Logic";const d=le(s),r=document.createElement("button");r.type="button",r.className="btn btn-xs btn-ghost",w(r,d?"chevron-down":"chevron-right",12);const a=document.createElement("div");a.className="fb-logic-body",a.style.cssText="padding:.25rem 0 .25rem;"+(d?"":"display:none;"),l.addEventListener("click",()=>{const c=a.style.display==="none";a.style.display=c?"":"none",w(r,c?"chevron-down":"chevron-right",12)}),l.appendChild(i),l.appendChild(r),o.appendChild(l),a.appendChild(we(s.visibility||{},t,n)),a.appendChild(ke(s.requirement||{},t,n)),a.appendChild(Se(s.validation||[],t,n));const p=document.getElementById(`fb-type-${t}`)?.value||e.type;return W.has(p)&&a.appendChild(Te(s.cascade||{},t,n)),o.appendChild(a),o}function se(e,t,s){const n=document.createElement("div");return n.className="fb-field-extras",e==="chooser"?(n.appendChild(qe(t,s)),n.appendChild(Le(t.options||[],s))):(W.has(e)&&n.appendChild(Fe(t.options||[],s)),e==="textarea"&&n.appendChild(k([S("Rows",`fb-rows-${s}`,"number",t.formConfig?.rows||4,"Height of textarea")])),(e==="string"||e==="textarea")&&n.appendChild(k([S("Min Length",`fb-minlength-${s}`,"number",t.minLength||"",""),S("Max Length",`fb-maxlength-${s}`,"number",t.maxLength||"","")])),e==="number"&&n.appendChild(k([S("Min",`fb-min-${s}`,"number",t.min??"",""),S("Max",`fb-max-${s}`,"number",t.max??"","")]))),n.children.length?n:null}function ie(e){const t=document.createElement("div");t.className="fb-chooser-section",t.style.cssText="border:1px solid var(--dm-border, #e5e7eb); border-radius:6px; margin-top:.5rem; overflow:hidden;";const s=document.createElement("div");s.style.cssText="padding:.4rem .6rem; background:color-mix(in srgb, var(--dm-background, #f9fafb) 60%, transparent); font-weight:600; font-size:.82rem; border-bottom:1px solid var(--dm-border, #e5e7eb);",s.textContent=e,t.appendChild(s);const n=document.createElement("div");return n.className="fb-chooser-section-body",n.style.cssText="padding:.55rem .6rem; display:flex; flex-direction:column; gap:.45rem;",t.appendChild(n),{wrap:t,body:n}}function U(e,t,s,n,o){const l=document.createElement("div");l.style.flex="1";const i=document.createElement("label");i.htmlFor=e,i.className="form-label",i.textContent=t,i.style.fontSize=".8rem";const d=document.createElement("select");if(d.id=e,d.className="form-input",n.forEach(([r,a])=>{const p=document.createElement("option");p.value=r,p.textContent=a,String(r)===String(s)&&(p.selected=!0),d.appendChild(p)}),l.appendChild(i),l.appendChild(d),o){const r=document.createElement("p");r.className="form-hint text-muted",r.textContent=o,r.style.cssText="font-size:.72rem;margin-top:.2rem;",l.appendChild(r)}return l}function ce(e,t,s,n){const o=document.createElement("div");o.style.flex="1";const l=document.createElement("label");l.style.cssText="display:inline-flex;align-items:center;gap:.4rem;font-size:.82rem;";const i=document.createElement("input");if(i.id=e,i.type="checkbox",i.checked=!!s,l.appendChild(i),l.appendChild(document.createTextNode(" "+t)),o.appendChild(l),n){const d=document.createElement("p");d.className="form-hint text-muted",d.textContent=n,d.style.cssText="font-size:.72rem;margin-top:.2rem;",o.appendChild(d)}return o}function K(e,t,s,n,o){const l=document.createElement("div");l.style.flex="1";const i=document.createElement("label");i.htmlFor=e,i.className="form-label",i.textContent=t,i.style.fontSize=".8rem",l.appendChild(i);const d=document.createElement("div");d.style.cssText="display:flex;gap:.4rem;align-items:stretch;";const r=document.createElement("input");r.id=e,r.type="text",r.className="form-input",r.value=s||"",n&&(r.placeholder=n),r.style.flex="1";const a=document.createElement("input");a.type="color",a.title="Pick a colour",a.style.cssText="width:36px;height:auto;padding:0;border:1px solid var(--dm-border,#e5e7eb);border-radius:4px;cursor:pointer;";const p=String(r.value).match(/^#[0-9a-fA-F]{3,8}$/);if(a.value=p?r.value:"#000000",a.addEventListener("input",()=>{r.value=a.value,r.dispatchEvent(new Event("input",{bubbles:!0}))}),d.appendChild(r),d.appendChild(a),l.appendChild(d),o){const c=document.createElement("p");c.className="form-hint text-muted",c.textContent=o,c.style.cssText="font-size:.72rem;margin-top:.2rem;",l.appendChild(c)}return l}function qe(e,t){const s=document.createElement("div");s.style.cssText="margin-top:.5rem; display:flex; flex-direction:column; gap:.4rem;";const n=ie("Layout");n.body.appendChild(k([U(`fb-chooser-variant-${t}`,"Variant",e.variant||"card",[["card","Card \u2014 rich tiles"],["chip","Chip \u2014 compact pills"]],"Card supports description and grid columns; chips wrap."),ce(`fb-chooser-multiple-${t}`,"Multi-select",!!e.multiple,"Off = single (radio); on = multi (checkbox).")])),n.body.appendChild(k([U(`fb-chooser-density-${t}`,"Density",e.density||"comfortable",[["comfortable","Comfortable"],["compact","Compact"]],"Compact strips description (card variant only)."),S("Columns",`fb-chooser-columns-${t}`,"number",e.columns||3,"Card grid (1\u20136). Ignored for chips.")])),s.appendChild(n.wrap);const o=ie("Visual");return o.body.appendChild(k([K(`fb-chooser-accent-${t}`,"Accent",e.accent||"","primary / success / #ec4899","Selected highlight colour \u2014 semantic name or any CSS colour."),U(`fb-chooser-accent-style-${t}`,"Accent style",e.accentStyle||"border",[["border","Border (default)"],["solid","Solid"],["glow","Glow"],["overlay","Overlay"],["underline","Underline"]],"Visual treatment of the selected state.")])),o.body.appendChild(k([ce(`fb-chooser-glow-${t}`,"Glow on selected",!!e.glow,"Soft outer glow."),K(`fb-chooser-glow-colour-${t}`,"Glow colour",e.glowColour||"","defaults to accent","Optional; semantic name or CSS colour.")])),o.body.appendChild(k([U(`fb-chooser-shadow-${t}`,"Shadow",e.shadow||"none",[["none","None"],["sm","Small"],["md","Medium"],["lg","Large"],["xl","Extra Large"]],"Drop shadow on every option."),K(`fb-chooser-shadow-colour-${t}`,"Shadow colour",e.shadowColour||"","rgba(0,0,0,0.1)","Optional shadow tint.")])),s.appendChild(o.wrap),s}function $e(e,t,s,n){const o=document.createElement("div");o.className="fb-chooser-option-row",o.dataset.optIdx=String(s),o.style.cssText="border:1px solid var(--dm-border,#e5e7eb); border-radius:4px; padding:.5rem; background:var(--dm-background-alt, color-mix(in srgb, var(--dm-card-bg, #fff) 92%, var(--dm-text, #111))); display:flex; flex-direction:column; gap:.35rem;";const l=document.createElement("div");l.style.cssText="display:flex; gap:.4rem; align-items:center;";const i=document.createElement("div");i.style.cssText="display:flex; flex-direction:column; gap:1px;";const d=document.createElement("button");d.type="button",d.className="btn btn-xs btn-ghost",d.title="Move up",w(d,"arrow-up"),d.addEventListener("click",N=>{N.preventDefault(),re(t,s,-1,n)});const r=document.createElement("button");r.type="button",r.className="btn btn-xs btn-ghost",r.title="Move down",w(r,"arrow-down"),r.addEventListener("click",N=>{N.preventDefault(),re(t,s,1,n)}),i.appendChild(d),i.appendChild(r),l.appendChild(i);const a=document.createElement("input");a.type="text",a.className="form-input fb-chooser-opt-value",a.placeholder="value",a.value=e.value||"",a.style.flex="1",l.appendChild(a);const p=document.createElement("input");p.type="text",p.className="form-input fb-chooser-opt-label",p.placeholder="label (visible to users)",p.value=e.label||"",p.style.flex="2",l.appendChild(p);const c=document.createElement("button");c.type="button",c.className="btn btn-xs btn-ghost",c.style.cssText="line-height:1;opacity:.6;",c.title="Remove option",w(c,"x"),c.addEventListener("click",N=>{N.preventDefault(),Be(t,s,n)}),l.appendChild(c),o.appendChild(l);const u=document.createElement("div");u.style.cssText="display:flex; gap:.4rem;";const m=fe("icon name (optional)",e.icon||"");m.input.classList.add("fb-chooser-opt-icon"),m.el.style.flex="1",u.appendChild(m.el);const f=document.createElement("input");f.type="text",f.className="form-input fb-chooser-opt-description",f.placeholder="description (card + comfortable only)",f.value=e.description||"",f.style.flex="2",u.appendChild(f),o.appendChild(u);const y=document.createElement("div");y.style.cssText="display:flex; gap:.4rem;";const h=document.createElement("input");h.type="text",h.className="form-input fb-chooser-opt-tooltip",h.placeholder="tooltip (hover hint)",h.value=e.tooltip||"",h.style.flex="2",y.appendChild(h);const b=document.createElement("input");b.type="text",b.className="form-input fb-chooser-opt-badge-text",b.placeholder="badge text",b.value=e.badge?.text||"",b.style.flex="1",y.appendChild(b);const v=document.createElement("select");v.className="form-input fb-chooser-opt-badge-type",v.style.flex="1",[["","no badge"],["primary","primary"],["success","success"],["info","info"],["warning","warning"],["danger","danger"]].forEach(([N,O])=>{const j=document.createElement("option");j.value=N,j.textContent=O,(e.badge?.type||"")===N&&(j.selected=!0),v.appendChild(j)}),y.appendChild(v),o.appendChild(y);const C=document.createElement("div");C.style.cssText="display:flex; gap:1rem; font-size:.8rem;";const x=document.createElement("label");x.style.cssText="display:inline-flex;align-items:center;gap:.3rem;";const T=document.createElement("input");T.type="checkbox",T.className="fb-chooser-opt-recommended",T.checked=!!e.recommended,x.appendChild(T),x.appendChild(document.createTextNode(" Recommended")),C.appendChild(x);const z=document.createElement("label");z.style.cssText="display:inline-flex;align-items:center;gap:.3rem;";const A=document.createElement("input");return A.type="checkbox",A.className="fb-chooser-opt-disabled",A.checked=!!e.disabled,z.appendChild(A),z.appendChild(document.createTextNode(" Disabled")),C.appendChild(z),o.appendChild(C),o}function Le(e,t){const s=document.createElement("div");s.style.cssText="margin-top:.5rem;";const n=document.createElement("div");n.style.cssText="display:flex; align-items:center; justify-content:space-between; margin-bottom:.4rem;";const o=document.createElement("p");o.textContent="Options",o.style.cssText="font-size:.85rem;font-weight:600;margin:0;",n.appendChild(o);const l=document.createElement("button");l.type="button",l.className="btn btn-xs btn-ghost",D(l,"plus","Add option"),n.appendChild(l),s.appendChild(n);const i=document.createElement("div");i.className="fb-chooser-options-list",i.id=`fb-chooser-options-${t}`,i.style.cssText="display:flex; flex-direction:column; gap:.4rem;",s.appendChild(i);const d=()=>{i.replaceChildren();const r=ze(t);(r.length?r:e||[]).forEach((p,c)=>{i.appendChild($e(p,t,c,d))})};return l.addEventListener("click",r=>{r.preventDefault(),Ae(t,d)}),d(),s}function ze(e){const t=g[e];return!t||!Array.isArray(t.options)?[]:t.options}function X(e){return g[e]?(Array.isArray(g[e].options)||(g[e].options=[]),g[e].options):[]}function Ae(e,t){J(e),X(e).push({value:"new",label:"New option"}),t()}function Be(e,t,s){J(e),X(e).splice(t,1),s()}function re(e,t,s,n){J(e);const o=X(e),l=t+s;if(l<0||l>=o.length)return;const[i]=o.splice(t,1);o.splice(l,0,i),n()}function J(e){const t=document.getElementById(`fb-chooser-options-${e}`);if(!t)return;const s=t.querySelectorAll(".fb-chooser-option-row"),n=[];s.forEach(o=>{const l={value:o.querySelector(".fb-chooser-opt-value")?.value.trim()||"",label:o.querySelector(".fb-chooser-opt-label")?.value.trim()||""},i=o.querySelector(".fb-chooser-opt-icon")?.value.trim();i&&(l.icon=i);const d=o.querySelector(".fb-chooser-opt-description")?.value.trim();d&&(l.description=d);const r=o.querySelector(".fb-chooser-opt-tooltip")?.value.trim();r&&(l.tooltip=r);const a=o.querySelector(".fb-chooser-opt-badge-text")?.value.trim(),p=o.querySelector(".fb-chooser-opt-badge-type")?.value;(a||p)&&(l.badge={},a&&(l.badge.text=a),p&&(l.badge.type=p)),o.querySelector(".fb-chooser-opt-recommended")?.checked&&(l.recommended=!0),o.querySelector(".fb-chooser-opt-disabled")?.checked&&(l.disabled=!0),n.push(l)}),g[e]&&(g[e].options=n)}function k(e){const t=document.createElement("div");return t.style.cssText="display:flex;gap:.85rem;margin-bottom:.75rem;",e.forEach(s=>{s&&t.appendChild(s)}),t}function S(e,t,s,n,o){const l=document.createElement("div");l.style.flex="1";const i=document.createElement("label");i.htmlFor=t,i.className="form-label",i.textContent=e,i.style.fontSize=".8rem";const d=document.createElement("input");if(d.id=t,d.type=s||"text",d.className="form-input",d.value=n??"",l.appendChild(i),l.appendChild(d),o){const r=document.createElement("p");r.className="form-hint text-muted",r.textContent=o,r.style.cssText="font-size:.73rem;margin-top:.2rem;",l.appendChild(r)}return l}function Ie(e,t,s,n){const o=document.createElement("div");o.style.flex="1";const l=document.createElement("label");l.htmlFor=t,l.className="form-label",l.textContent=e,l.style.fontSize=".8rem";const i=document.createElement("select");return i.id=t,i.className="form-select",s.forEach(d=>{const r=document.createElement("option");r.value=d.value,r.textContent=d.label,d.value===n&&(r.selected=!0),i.appendChild(r)}),o.appendChild(l),o.appendChild(i),o}function de(e,t,s){const n=document.createElement("div");n.style.cssText="flex:0;min-width:80px;display:flex;flex-direction:column;justify-content:flex-end;";const o=document.createElement("label");o.style.cssText="display:flex;align-items:center;gap:.4rem;cursor:pointer;font-size:.8rem;white-space:nowrap;";const l=document.createElement("input");return l.id=t,l.type="checkbox",l.checked=s,o.appendChild(l),o.appendChild(document.createTextNode(e)),n.appendChild(o),n}function Fe(e,t){const s=document.createElement("div");s.style.cssText="margin-top:.4rem;";const n=document.createElement("p");n.textContent="Options (one per line: value or value:Label)",n.style.cssText="font-size:.8rem;font-weight:600;margin-bottom:.3rem;";const o=document.createElement("textarea");return o.id=`fb-options-${t}`,o.className="form-input",o.rows=4,o.placeholder=`yes:Yes
|
|
6
6
|
no:No
|
|
7
7
|
maybe:Maybe`,o.value=(e||[]).map(l=>{const i=typeof l=="string"?l:l.value??"",d=typeof l=="string"?l:l.label??i;return i===d?i:`${i}:${d}`}).join(`
|
|
8
|
-
`),o.style.fontFamily="monospace",s.appendChild(
|
|
8
|
+
`),o.style.fontFamily="monospace",s.appendChild(n),s.appendChild(o),s}function pe(e){const t=e.find("#field-project").val()||"";return $(),{title:e.find("#field-title").val().trim(),slug:e.find("#field-slug").val().trim(),description:e.find("#field-description").val().trim(),...e.find("#field-bundled").is(":checked")?{bundled:!0}:{},meta:{...G||{},project:t||null},fields:g,settings:{submitText:e.find("#setting-submit-text").val().trim()||"Submit",successMessage:e.find("#setting-success-message").val().trim()||"Thank you.",layout:e.find("#setting-layout").val()||"stacked",columns:parseInt(e.find("#setting-columns").val(),10)||2,submitSpan:e.find("#setting-submit-span").val()||"",honeypot:e.find("#setting-honeypot").prop("checked"),rateLimitPerMinute:parseInt(e.find("#setting-rate-limit").val(),10)||3,successRedirect:e.find("#setting-success-redirect").val().trim()||null,actionSlug:e.find("#action-cms-slug").val()||null},actions:{email:{enabled:e.find("#action-email-enabled").prop("checked"),recipients:e.find("#action-email-recipients").val().trim(),subjectPrefix:e.find("#action-email-subject-prefix").val().trim()},webhook:{enabled:e.find("#action-webhook-enabled").prop("checked"),url:e.find("#action-webhook-url").val().trim(),method:e.find("#action-webhook-method").val()},...P!==null&&{collection:P}}}}function Oe(e){const t=[];let s=[],n="Step 1",o="";return e.forEach(l=>{l.type==="page-break"?(t.push({title:n,description:o,fields:s}),s=[],n=l.label||`Step ${t.length+1}`,o=l.description||""):l.type!=="spacer"&&s.push(l)}),(s.length||t.length===0)&&t.push({title:n,description:o,fields:s}),t}function me(e,t){const s={};return e.forEach(n=>{if(n.type==="page-break"||n.type==="spacer")return;const o={...n.formConfig||{}};o.span==="full"&&t&&(o.span=t);const l=n.type==="chooser"||!!(n.variant&&["select","radio","checkbox-group"].includes(n.type)),d={type:n.type==="checkbox"?"boolean":n.type==="date"?"string":l?"chooser":n.type,label:n.label,required:n.required,options:n.options,formConfig:{...n.placeholder&&{placeholder:n.placeholder},...n.helper&&{helperText:n.helper},...n.tooltip&&{tooltip:n.tooltip},...o}};l&&(n.variant&&(d.variant=n.variant),(n.multiple||n.type==="checkbox-group")&&(d.multiple=!0),n.density&&(d.density=n.density),n.columns&&(d.columns=n.columns),n.accent&&(d.accent=n.accent),n.accentStyle&&(d.accentStyle=n.accentStyle),n.glow&&(d.glow=!0),n.glowColour&&(d.glowColour=n.glowColour),n.shadow&&(d.shadow=n.shadow),n.shadowColour&&(d.shadowColour=n.shadowColour)),s[n.name]=d}),s}function ue(e,t){const s=typeof e=="string"?document.querySelector(e):e;s&&(t||[]).forEach(n=>{if(n.type!=="date"||!n.name)return;const o=s.querySelector(`[name="${n.name}"]`);o&&o.type!=="date"&&(o.type="date")})}export const formEditorView={templateUrl:"/admin/js/templates/form-editor.html",async onMount(e){g=[],q=null,P=null;const t=window.location.hash.match(/\/forms\/edit\/([^/?#]+)/);q=t?t[1]:null;let s=null;const n=E.loader(e.get(0),{type:"dots"});if(q)try{s=await I(`/forms/${q}`),g=s.fields||[],P=s.actions?.collection??null}catch{E.toast("Could not load form.",{type:"error"})}G={};try{const a=await I("/projects").catch(()=>[]),p=e.find("#field-project").get(0);p&&(Array.isArray(a)?a:[]).forEach(c=>{const u=document.createElement("option");u.value=c.slug,u.textContent=c.name||c.slug,p.appendChild(u)})}catch{}if(n.destroy(),s?e.find("#editor-title").get(0).textContent=`Edit: ${s.title}`:e.find("#editor-title").get(0).textContent="New Form",q||e.find("#field-title").get(0).addEventListener("input",function(){e.find("#field-slug").val(te(this.value))}),E.tabs(e.find("#editor-tabs").get(0)),s){e.find("#field-title").val(s.title),e.find("#field-slug").val(s.slug),e.find("#field-description").val(s.description||""),e.find("#field-bundled").prop("checked",!!s.bundled),G=s.meta||{},e.find("#field-project").val(s.meta?.project||"");const a=s.settings||{};e.find("#setting-submit-text").val(a.submitText||"Submit"),e.find("#setting-success-message").val(a.successMessage||""),e.find("#setting-layout").val(a.layout||"stacked"),e.find("#setting-columns").val(a.columns||2),e.find("#setting-submit-span").val(a.submitSpan||""),e.find("#columns-group").get(0).style.display=a.layout==="grid"?"":"none",e.find("#setting-honeypot").prop("checked",a.honeypot!==!1),e.find("#setting-rate-limit").val(a.rateLimitPerMinute||3),e.find("#setting-success-redirect").val(a.successRedirect||"");const p=s.actions?.email||{};e.find("#action-email-enabled").prop("checked",p.enabled||!1),e.find("#action-email-recipients").val(p.recipients||""),e.find("#action-email-subject-prefix").val(p.subjectPrefix||"");const c=s.actions?.webhook||{};e.find("#action-webhook-enabled").prop("checked",c.enabled||!1),e.find("#action-webhook-url").val(c.url||""),e.find("#action-webhook-method").val(c.method||"POST")}const o=s?.settings?.actionSlug||"";try{const a=await I("/actions").catch(()=>[]),p=e.find("#action-cms-slug").get(0);if((Array.isArray(a)?a:[]).forEach(c=>{const u=document.createElement("option");u.value=c.slug,u.textContent=c.title||c.slug,c.slug===o&&(u.selected=!0),p.appendChild(u)}),!a.length){const c=document.createElement("option");c.value="",c.textContent="No actions available",c.disabled=!0,p.appendChild(c)}}catch{}e.find("#setting-layout").get(0)?.addEventListener("change",function(){const a=this.value==="grid";e.find("#columns-group").get(0).style.display=a?"":"none",document.querySelectorAll(".fb-grid-row").forEach(p=>{p.style.display=a?"flex":"none"})}),L(e);const l=e.find("#add-element-menu").get(0);e.find("#add-element-btn").get(0).addEventListener("click",a=>{a.stopPropagation(),l.style.display=l.style.display==="none"?"":"none"}),M&&document.removeEventListener("click",M),M=()=>{l&&(l.style.display="none")},document.addEventListener("click",M),e.find("#add-field-btn").get(0).addEventListener("click",()=>{l&&(l.style.display="none"),$();const a=g.length;g.push({name:`field_${a+1}`,type:"string",label:"New Field",required:!1,placeholder:""}),L(e);const p=e.find("#fields-list").get(0)?.lastElementChild;if(p){const c=p.querySelector(".fb-field-body");c&&(c.style.display="")}}),e.find("#add-spacer-btn").get(0).addEventListener("click",()=>{l&&(l.style.display="none"),$(),g.push({type:"spacer"}),L(e)}),e.find("#add-page-break-btn").get(0).addEventListener("click",()=>{l&&(l.style.display="none"),$();const a=g.filter(p=>p.type==="page-break").length+2;g.push({type:"page-break",label:`Step ${a}`,description:""}),L(e)}),e.find("#save-form-btn").get(0).addEventListener("click",async()=>{const a=pe(e);if(!a.title){E.toast("Please enter a form title.",{type:"error"});return}try{q?(await I(`/forms/${q}`,{method:"PUT",body:JSON.stringify(a)}),E.toast("Form saved.",{type:"success"})):(q=(await I("/forms",{method:"POST",body:JSON.stringify(a)})).slug,R.navigate(`/forms/edit/${q}`),E.toast("Form created.",{type:"success"}))}catch(p){E.toast(p.message||"Failed to save form.",{type:"error"})}}),_=()=>{const a=pe(e),p=e.find("#preview-container").get(0);if(!p)return;const c=e.find("#preview-test-result").get(0),u=e.find("#preview-test-badge").get(0);c&&(c.style.display="none",c.textContent=""),u&&(u.style.display=q?"":"none"),e.find("#preview-card").get(0).style.display="",p.textContent="";const m=document.createElement("div");m.id="fb-preview-form",p.appendChild(m);const f=q?async h=>{c&&(c.style.display="none",c.textContent="");try{const b=await fetch(`/api/forms/submit/${q}`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(h)}),v=await b.json();if(!b.ok)throw new Error(v.error||"Submission failed.");c&&(c.textContent=v.message||a.settings?.successMessage||"Submitted successfully.",c.style.cssText="display:block;margin-top:.75rem;padding:.6rem .9rem;border-radius:6px;font-size:.9rem;background:color-mix(in srgb, var(--dm-success) 12%, transparent);color:var(--dm-success,#22c55e);"),E.toast("Test submission stored.",{type:"success"})}catch(b){c&&(c.textContent=b.message,c.style.cssText="display:block;margin-top:.75rem;padding:.6rem .9rem;border-radius:6px;font-size:.9rem;background:color-mix(in srgb, var(--dm-danger) 12%, transparent);color:var(--dm-danger,#ef4444);"),E.toast(b.message,{type:"error"})}return!1}:()=>!1,y=a.fields.some(h=>h.type==="page-break");if(typeof F<"u"){const h=a.settings?.columns||2,b=a.settings?.layout||"stacked";if(y&&F.wizard){const v=Oe(a.fields).map(C=>({title:C.title,description:C.description,fields:me(C.fields,h)}));F.wizard("#fb-preview-form",{schema:{steps:v},onSubmit:f}),ue("#fb-preview-form",a.fields)}else if(F.render){const v=me(a.fields,h),C={};if(a.fields.forEach(x=>{if(!(!x.name||x.type==="page-break"||x.type==="spacer")&&(x.type==="select"||x.type==="multiselect")&&x.required){const T=(x.options||[])[0];T&&(C[x.name]=typeof T=="object"?T.value:T)}}),F.render("#fb-preview-form",v,C,{submitText:a.settings?.submitText||"Submit",layout:b,columns:h,onSubmit:f}),b==="grid"&&a.settings?.submitSpan==="full"){const x=document.querySelector("#fb-preview-form .form-buttons");x&&x.classList.add("col-span-full")}ue("#fb-preview-form",a.fields)}window.FormLogicEngine&&a.fields.some(v=>v.logic)&&requestAnimationFrame(()=>{new window.FormLogicEngine.FormLogicRuntime({fields:a.fields},m).init()})}else{const h=document.createElement("p");h.textContent=`${a.fields.filter(b=>b.type!=="page-break").length} field(s): ${a.fields.filter(b=>b.type!=="page-break").map(b=>b.label).join(", ")}`,h.style.cssText="color:var(--dm-text-muted);font-style:italic;",m.appendChild(h)}},_();const i=e.find("#editor-tabs").get(0),d=e.find("#preview-card").get(0),r=a=>{d&&d.contains(a.target)||Y()};i&&(i.addEventListener("input",r),i.addEventListener("change",r))}};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{apiRequest as x}from"/admin/js/api.js";import{keepTableIconsAlive as A}from"/admin/js/lib/table-icons.js";let c=null,k=[],r=[];export const formSubmissionsView={templateUrl:"/admin/js/templates/form-submissions.html",async onMount(e){const l=window.location.hash.match(/\/forms\/([^/?#]+)\/submissions/);if(c=l?l[1]:null,!c){E.toast("No form selected.",{type:"error"});return}const u=E.loader(e.get(0),{type:"dots"});try{const
|
|
1
|
+
import{apiRequest as x}from"/admin/js/api.js";import{keepTableIconsAlive as A}from"/admin/js/lib/table-icons.js";let c=null,k=[],r=[];export const formSubmissionsView={templateUrl:"/admin/js/templates/form-submissions.html",async onMount(e){const l=window.location.hash.match(/\/forms\/([^/?#]+)\/submissions/);if(c=l?l[1]:null,!c){E.toast("No form selected.",{type:"error"});return}const u=E.loader(e.get(0),{type:"dots"});try{const o=await x(`/forms/${c}`);k=(o.fields||[]).filter(s=>s.type!=="page-break"),e.find("#submissions-title").get(0).textContent=`${o.title} \u2014 Submissions`}catch{E.toast("Could not load form definition.",{type:"error"})}try{await M(e)}finally{u.destroy()}A(e.get(0)),e.off("click",".js-delete-submission").on("click",".js-delete-submission",async function(o){o.stopPropagation();const s=this.dataset.id;if(await E.confirm("Delete this submission?"))try{await x(`/forms/${c}/submissions/${s}`,{method:"DELETE"}),r=r.filter(d=>d.id!==s),E.toast("Submission deleted.",{type:"success"}),v(r,e)}catch{E.toast("Failed to delete submission.",{type:"error"})}}),e.find("#export-btn").off("click").on("click",()=>{const o=E.slideover({title:"Export Submissions",size:"sm",position:"right"}),s=document.createElement("div");s.style.cssText="padding:1.25rem;display:flex;flex-direction:column;gap:1rem;";function i(d,y,f,g){const C=document.createElement("div");C.style.cssText="display:flex;flex-direction:column;gap:.4rem;";const b=document.createElement("button");b.className="btn btn-ghost",b.style.cssText="justify-content:flex-start;gap:.5rem;";const L=document.createElement("span");return L.setAttribute("data-icon",y),b.appendChild(L),b.appendChild(document.createTextNode(" "+d)),b.addEventListener("click",()=>{fetch(f,{headers:{Authorization:"Bearer "+(S.get("auth_token")||"")}}).then(w=>w.blob()).then(w=>{const h=document.createElement("a");h.href=URL.createObjectURL(w),h.download=g,document.body.appendChild(h),h.click(),document.body.removeChild(h),URL.revokeObjectURL(h.href),o.close()}).catch(()=>E.toast("Export failed.",{type:"error"}))}),C.appendChild(b),C}s.appendChild(i("Export as CSV","file-text",`/api/forms/${c}/submissions/export`,`${c}-submissions.csv`)),s.appendChild(i("Export as JSON","code",`/api/forms/${c}/submissions/export/json`,`${c}-submissions.json`)),o.setContent(s),Domma.icons.scan(s),o.open()}),e.find("#clear-all-btn").off("click").on("click",async()=>{if(await E.confirm("Delete all submissions? This cannot be undone."))try{await x(`/forms/${c}/submissions`,{method:"DELETE"}),r=[],E.toast("All submissions cleared.",{type:"success"}),v([],e)}catch{E.toast("Failed to clear submissions.",{type:"error"})}});const m=e.find("#sub-search").get(0),n=e.find("#sub-date-from").get(0),t=e.find("#sub-date-to").get(0);function a(){const o=m?.value.toLowerCase()||"",s=n?.value?new Date(n.value):null,i=t?.value?new Date(t.value+"T23:59:59"):null,d=r.filter(y=>{if(o&&!Object.values(y.data||{}).map(g=>String(g).toLowerCase()).some(g=>g.includes(o)))return!1;if(s||i){const f=y.meta?.createdAt?new Date(y.meta.createdAt):null;if(!f||s&&f<s||i&&f>i)return!1}return!0});v(d,e)}m&&m.addEventListener("input",a),n&&n.addEventListener("change",a),t&&t.addEventListener("change",a),Domma.icons.scan()}};async function M(e){try{r=await x(`/forms/${c}/submissions`)}catch{r=[],E.toast("Could not load submissions.",{type:"error"})}v(r,e)}function v(e,p){const l=p.find("#sub-count").get(0);l&&(e.length===r.length?l.textContent=`${r.length} submission${r.length!==1?"s":""}`:l.textContent=`Showing ${e.length} of ${r.length}`);const m=[...k.map(t=>({key:`data.${t.name}`,title:t.label||t.name,render:(a,o)=>{const s=o.data?.[t.name]??"",i=String(s),d=document.createElement("span");return d.title=i,d.textContent=i.length>80?i.slice(0,80)+"\u2026":i,d.outerHTML}})),{key:"meta",title:"Date",render:t=>D(t?.createdAt).format("DD MMM YYYY HH:mm")},{key:"id",title:"",render:t=>{const a=document.createElement("button");return a.className="btn btn-sm btn-ghost js-delete-submission",a.dataset.id=t,a.setAttribute("data-tooltip","Delete"),a.innerHTML='<span data-icon="trash" data-icon-size="14"></span>',a.outerHTML}}];T.create("#submissions-table",{data:e,columns:m,emptyMessage:"No submissions yet."});const n=document.querySelector("#submissions-table");n&&(n.querySelectorAll("tbody tr").forEach((t,a)=>{const o=e[a];o&&(t.style.cursor="pointer",t.addEventListener("click",s=>{s.target.closest(".js-delete-submission")||j(o)}))}),Domma.icons.scan(n),n.querySelectorAll("[data-tooltip]").forEach(t=>{E.tooltip(t,{content:t.getAttribute("data-tooltip"),position:"top"})}))}function j(e){const p=document.createElement("div"),l=document.createElement("div");l.style.cssText="display:flex;flex-direction:column;gap:.75rem;margin-bottom:1.25rem;",k.forEach(n=>{const t=e.data?.[n.name];if(t==null||t==="")return;const a=document.createElement("div");a.style.cssText="border-bottom:1px solid var(--dm-border,#333);padding-bottom:.6rem;";const o=document.createElement("strong");o.textContent=n.label||n.name,o.style.cssText="display:block;font-size:.8rem;color:var(--dm-text-muted,#888);margin-bottom:.2rem;";const s=document.createElement("p");s.textContent=String(t),s.style.cssText="margin:0;word-break:break-word;",a.appendChild(o),a.appendChild(s),l.appendChild(a)});const u=document.createElement("div");if(u.style.cssText="font-size:.8rem;color:var(--dm-text-muted,#888);display:flex;flex-direction:column;gap:.3rem;border-top:1px solid var(--dm-border,#333);padding-top:.75rem;",e.meta?.createdAt){const n=document.createElement("span");n.textContent=`Submitted: ${D(e.meta.createdAt).format("DD MMM YYYY HH:mm")}`,u.appendChild(n)}if(e.meta?.ip){const n=document.createElement("span");n.textContent=`IP: ${e.meta.ip}`,u.appendChild(n)}p.appendChild(l),p.appendChild(u);const m=E.modal({title:"Submission Details",size:"md"});m.element.appendChild(p),m.open()}
|
package/admin/js/views/forms.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{apiRequest as p}from"/admin/js/api.js";import{filterByProject as
|
|
1
|
+
import{apiRequest as p}from"/admin/js/api.js";import{filterByProject as b,getProjectFromHash as h}from"/admin/js/lib/project-context.js";import{keepTableIconsAlive as C}from"/admin/js/lib/table-icons.js";function u(d){return String(d).replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""").replace(/'/g,"'")}export const formsView={templateUrl:"/admin/js/templates/forms.html",async onMount(d){await g(d),C(d.get(0)),d.off("click",".js-delete-form").on("click",".js-delete-form",async function(){const r=this.dataset.slug,e=this.dataset.plugin,c=e?`This form is managed by the <strong>${e}</strong> plugin. Deleting it may cause the plugin to malfunction. Continue?`:`Delete form "${r}" and all its submissions? This cannot be undone.`;if(await E.confirm(c))try{await p(`/forms/${r}`,{method:"DELETE"}),E.toast("Form deleted.",{type:"success"}),await g(d)}catch{E.toast("Failed to delete form.",{type:"error"})}}),d.off("click",".js-clone-form").on("click",".js-clone-form",async function(){const r=this.dataset.slug;try{const e=await p(`/forms/${r}`),{collection:c,...t}=e.actions||{},n=await p("/forms",{method:"POST",body:JSON.stringify({title:`${e.title} (Copy)`,description:e.description||"",fields:e.fields||[],settings:e.settings||{},actions:t})});E.toast(`Form duplicated as "${n.title}".`,{type:"success"}),R.navigate(`/forms/edit/${n.slug}`)}catch(e){E.toast(e.message||"Failed to duplicate form.",{type:"error"})}}),d.find("#create-form-btn").off("click").on("click",()=>{const r=E.modal({title:"Create Form",size:"sm"}),e=document.createElement("div");e.style.cssText="padding:.25rem 0 .5rem;";const c=document.createElement("div");F.create({title:{type:"string",label:"Form Title",placeholder:"e.g. Contact, Feedback\u2026",required:!0}},{},{showSubmitButton:!1}).renderTo(c),e.appendChild(c);const t=document.createElement("div");t.style.cssText="display:flex;justify-content:flex-end;gap:.5rem;margin-top:.75rem;";const n=document.createElement("button");n.className="btn btn-ghost",n.textContent="Cancel";const o=document.createElement("button");o.className="btn btn-primary",o.textContent="Create",t.appendChild(n),t.appendChild(o),e.appendChild(t),r.element.appendChild(e),r.open();const s=c.querySelector('input[name="title"]');setTimeout(()=>s?.focus(),50);async function a(){const i=s?.value.trim();if(i)try{const l=await p("/forms",{method:"POST",body:JSON.stringify({title:i})});r.close(),R.navigate(`/forms/edit/${l.slug}`)}catch(l){E.toast(l.message||"Failed to create form.",{type:"error"})}}n.addEventListener("click",()=>r.close()),o.addEventListener("click",a),s?.addEventListener("keydown",i=>{i.key==="Enter"&&a()})}),d.find("#test-email-btn").off("click").on("click",async()=>{const e=(window.__CMS_SITE__?.smtp||{}).fromAddress||"",c=E.modal({title:"Send Test Email",size:"sm"}),t=document.createElement("div");t.style.cssText="padding:.25rem 0 .5rem;";const n=document.createElement("div");n.className="mb-3";const o=document.createElement("label");o.className="form-label",o.textContent="Send to";const s=document.createElement("input");s.type="email",s.className="form-input",s.value=e,s.placeholder="test@example.com",n.appendChild(o),n.appendChild(s),t.appendChild(n);const a=document.createElement("p");a.className="text-muted",a.style.cssText="font-size:.8rem;margin-bottom:.75rem;",a.textContent="SMTP settings are configured in Site Settings.",t.appendChild(a);const i=document.createElement("div");i.style.cssText="display:flex;justify-content:flex-end;gap:.5rem;margin-top:.75rem;";const l=document.createElement("button");l.className="btn btn-ghost",l.textContent="Cancel";const m=document.createElement("button");m.className="btn btn-primary",m.textContent="Send",i.appendChild(l),i.appendChild(m),t.appendChild(i),c.element.appendChild(t),c.open(),setTimeout(()=>s?.focus(),50),l.addEventListener("click",()=>c.close()),m.addEventListener("click",async()=>{const f=s.value.trim();if(f){m.disabled=!0;try{await p("/forms/test-email",{method:"POST",body:JSON.stringify({to:f})}),c.close(),E.toast("Test email sent.",{type:"success"})}catch(y){E.toast(y.message||"Failed to send test email.",{type:"error"})}finally{m.disabled=!1}}})}),Domma.icons.scan()}};async function g(d){const r=E.loader(d.get(0),{type:"dots"});let e=[];try{e=await p("/forms")}catch{E.toast("Could not load forms.",{type:"error"})}finally{r.destroy()}const c=h();c&&(e=b(e,c)),T.create("#forms-table",{data:e,columns:[{key:"title",title:"Title",render:(t,n)=>{const o=document.createElement("span");o.style.cssText="display:inline-flex;align-items:center;gap:.4rem;flex-wrap:wrap;";const s=document.createElement("a");if(s.href=`#/forms/edit/${u(n.slug)}`,s.textContent=t,s.style.fontWeight="600",o.appendChild(s),n.plugin){const a=document.createElement("span");a.className="badge badge-outline",a.textContent=n.plugin,a.title=`Managed by the ${n.plugin} plugin`,a.style.cssText="font-size:0.65rem;padding:1px 6px;color:var(--dm-warning,#d97706);border-color:var(--dm-warning,#d97706);flex-shrink:0;",o.appendChild(a)}return o.outerHTML}},{key:"slug",title:"Slug",render:t=>{const n=document.createElement("code");return n.textContent=t,n.outerHTML}},{key:"fields",title:"Field Count",render:t=>String(t?.length??0)},{key:"submissionCount",title:"Submission Count",render:t=>String(t??0)},{key:"slug",title:"Actions",render:(t,n)=>{const o=document.createElement("div");o.style.cssText="display:flex;gap:.4rem;justify-content:flex-end;";const s=document.createElement("a");s.href=`#/forms/edit/${u(t)}`,s.className="btn btn-sm btn-ghost",s.setAttribute("data-tooltip","Edit"),s.innerHTML='<span data-icon="edit" data-icon-size="14"></span>';const a=document.createElement("a");a.href=`#/forms/${u(t)}/submissions`,a.className="btn btn-sm btn-ghost",a.setAttribute("data-tooltip","Submissions"),a.innerHTML='<span data-icon="inbox" data-icon-size="14"></span>';const i=document.createElement("button");i.className="btn btn-sm btn-ghost js-clone-form",i.dataset.slug=t,i.setAttribute("data-tooltip","Duplicate"),i.innerHTML='<span data-icon="copy" data-icon-size="14"></span>';const l=document.createElement("button");return l.className="btn btn-sm btn-ghost js-delete-form",l.dataset.slug=t,l.dataset.plugin=n.plugin||"",l.setAttribute("data-tooltip","Delete"),l.innerHTML='<span data-icon="trash" data-icon-size="14"></span>',o.appendChild(s),o.appendChild(a),o.appendChild(i),o.appendChild(l),o.outerHTML}}],emptyMessage:'No forms yet. Click "Create Form" to get started.'}),Domma.icons.scan(),document.querySelectorAll("#forms-table [data-tooltip]").forEach(t=>{E.tooltip(t,{content:t.getAttribute("data-tooltip"),position:"top"})})}
|
package/admin/js/views/media.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{api as m}from"../api.js";import{openImageEditor as B}from"../lib/image-editor.js";export const mediaView={templateUrl:"/admin/js/templates/media.html",async onMount(t){const i=c=>{const
|
|
1
|
+
import{api as m}from"../api.js";import{openImageEditor as B}from"../lib/image-editor.js";export const mediaView={templateUrl:"/admin/js/templates/media.html",async onMount(t){const i=c=>{const s=t.find("#media-grid").empty().get(0);if(!c.length){const e=document.createElement("p");e.className="text-muted",e.textContent="No media files yet. Upload one above.",s.appendChild(e);return}c.forEach(e=>{const r=/\.(png|jpe?g|gif|webp|svg)$/i.test(e.name),p=/\.(png|jpe?g|gif|webp|tiff?)$/i.test(e.name),n=document.createElement("div");n.className="media-card";const u=document.createElement("div");if(u.className="media-preview",r){const o=document.createElement("img");o.src=e.url+"?t="+Date.now(),o.alt=e.name,o.className="media-thumb",u.appendChild(o)}else{const o=document.createElement("div");o.className="media-thumb media-thumb--file";const w=document.createElement("span");w.setAttribute("data-icon","file"),o.appendChild(w),u.appendChild(o)}n.appendChild(u);const C=document.createElement("div");C.className="media-info";const l=document.createElement("span");l.className="media-name",l.title="Double-click to rename",l.textContent=e.name;const v=document.createElement("span");v.className="media-size",v.textContent=L(e.size),C.appendChild(l),C.appendChild(v),n.appendChild(C),l.addEventListener("dblclick",()=>D(l,e,i));const h=document.createElement("div");h.className="media-actions";const b=document.createElement("button");b.className="btn btn-sm btn-ghost";const N=document.createElement("span");N.setAttribute("data-icon","copy"),b.appendChild(N),b.addEventListener("click",()=>{navigator.clipboard.writeText(window.location.origin+e.url).then(()=>E.toast("URL copied.",{type:"success"})).catch(()=>E.toast("Copy failed.",{type:"error"}))});const g=document.createElement("button");g.className="btn btn-sm btn-ghost";const k=document.createElement("span");k.setAttribute("data-icon","type"),g.appendChild(k),g.addEventListener("click",()=>D(l,e,i));let d=null;if(p){d=document.createElement("button"),d.className="btn btn-sm btn-ghost";const o=document.createElement("span");o.setAttribute("data-icon","edit"),d.appendChild(o),d.addEventListener("click",async()=>{await B(e)&&i(await m.media.list().catch(()=>[]))})}const y=document.createElement("button");y.className="btn btn-sm btn-ghost";const x=document.createElement("span");x.setAttribute("data-icon","trash"),y.appendChild(x),y.addEventListener("click",async()=>{if(await E.confirm(`Delete "${e.name}"?`))try{await m.media.delete(e.name),E.toast("File deleted.",{type:"success"}),i(await m.media.list().catch(()=>[]))}catch{E.toast("Delete failed.",{type:"error"})}}),h.appendChild(b),h.appendChild(g),d&&h.appendChild(d),h.appendChild(y),n.appendChild(h),s.appendChild(n),E.tooltip(b,{content:"Copy URL",position:"top"}),E.tooltip(g,{content:"Rename",position:"top"}),d&&E.tooltip(d,{content:"Edit image",position:"top"}),E.tooltip(y,{content:"Delete file",position:"top"})}),Domma.icons.scan(),Domma.effects.reveal(".media-card",{animation:"fade",stagger:40,duration:350})},f=E.loader(t.get(0),{type:"dots"}),a=await m.media.list().catch(()=>[]);f.destroy(),i(a),Domma.effects.ripple("#upload-btn"),t.find("#upload-btn").on("click",()=>t.find("#file-input").get(0).click()),t.find("#file-input").on("change",async function(){if(!this.files.length)return;const c=new FormData;for(const r of this.files)c.append("file",r);const s=t.find("#upload-btn").get(0),e=s.lastChild;s.disabled=!0,e.textContent=" Uploading\u2026";try{if(!(await fetch("/api/media",{method:"POST",body:c,headers:{Authorization:"Bearer "+(S.get("auth_token")||"")}})).ok)throw new Error("Upload failed");E.toast("File uploaded.",{type:"success"}),i(await m.media.list().catch(()=>[]))}catch{E.toast("Upload failed.",{type:"error"})}finally{s.disabled=!1,e.textContent=" Upload",this.value=""}})}};function D(t,i,f){if(t.querySelector("input"))return;const a=i.name,c=a.lastIndexOf("."),s=c>0?a.slice(0,c):a,e=document.createElement("input");e.type="text",e.className="media-rename-input",e.value=a,e.title="",t.textContent="",t.appendChild(e),e.focus(),e.setSelectionRange(0,s.length);async function r(){const n=e.value.trim();if(!n||n===a)return p();e.disabled=!0;try{await m.media.rename(a,n),E.toast("File renamed.",{type:"success"}),f(await m.media.list().catch(()=>[]))}catch(u){E.toast(u.message||"Rename failed.",{type:"error"}),p()}}function p(){t.textContent=a,t.title="Double-click to rename"}e.addEventListener("keydown",n=>{n.key==="Enter"&&(n.preventDefault(),r()),n.key==="Escape"&&(n.preventDefault(),p())}),e.addEventListener("blur",()=>{setTimeout(()=>{t.contains(e)&&p()},150)})}function L(t){if(!t)return"0 B";const i=1024,f=["B","KB","MB","GB"],a=Math.floor(Math.log(t)/Math.log(i));return`${(t/Math.pow(i,a)).toFixed(1)} ${f[a]}`}
|
|
@@ -8,7 +8,7 @@ import{api as k}from"../api.js";import{colourToCss as U}from"/public/js/menu-dec
|
|
|
8
8
|
<button class="btn btn-sm btn-ghost me-indent" data-tooltip="Indent"><span data-icon="chevron-right"></span></button>
|
|
9
9
|
<button class="btn btn-sm btn-ghost me-up" data-tooltip="Move up"><span data-icon="arrow-up"></span></button>
|
|
10
10
|
<button class="btn btn-sm btn-ghost me-down" data-tooltip="Move down"><span data-icon="arrow-down"></span></button>
|
|
11
|
-
<button class="btn btn-sm btn-
|
|
11
|
+
<button class="btn btn-sm btn-ghost me-remove" data-tooltip="Remove"><span data-icon="trash"></span></button>
|
|
12
12
|
</div>
|
|
13
13
|
`);continue}const y=i.badge&&(i.badge.text||i.badge.countFrom)?`<span class="dm-menu-badge">${O(String(i.badge.text||"#"))}</span>`:"",w=U(i.colour),x=w?` style="color:${O(w)}"`:"";t.append(`
|
|
14
14
|
<div class="menu-tree-row menu-tree-row--slim${i.hidden?" menu-tree-row--hidden":""}" data-id="${i.id}" style="margin-left:${b*28}px">
|
|
@@ -23,6 +23,6 @@ import{api as k}from"../api.js";import{colourToCss as U}from"/public/js/menu-dec
|
|
|
23
23
|
<button class="btn btn-sm btn-ghost me-down" data-tooltip="Move down"><span data-icon="arrow-down"></span></button>
|
|
24
24
|
<button class="btn btn-sm btn-ghost me-hidden ${i.hidden?"active":""}" data-tooltip="${i.hidden?"Show":"Hide"}"><span data-icon="eye-off"></span></button>
|
|
25
25
|
<button class="btn btn-sm btn-secondary me-edit" data-tooltip="Edit"><span data-icon="edit"></span></button>
|
|
26
|
-
<button class="btn btn-sm btn-
|
|
26
|
+
<button class="btn btn-sm btn-ghost me-remove" data-tooltip="Remove"><span data-icon="trash"></span></button>
|
|
27
27
|
</div>
|
|
28
28
|
`),e(i.id)}}e(null),Domma.icons.scan(t.get(0)),t.get(0).querySelectorAll("[data-tooltip]").forEach(n=>{E.tooltip(n,{content:n.getAttribute("data-tooltip"),position:"top"})})}function S(t,r){t.find(".menu-tree-row").each(function(){const l=$(this).data("id"),e=r.find(x=>x.id===l);if(!e||e.type==="separator")return;const n=$(this).find(".me-text");n.length&&(e.text=n.val());const i=$(this).find(".me-url");i.length&&(e.url=i.val());const b=$(this).find(".me-icon");b.length&&(e.icon=b.val());const y=$(this).find(".me-vis");y.length&&(e.visibility=y.val());const w=$(this).find(".me-perm");w.length&&(e.permission=w.val())})}function it(t,r){const l={};for(const e of r){const n=t.querySelector(`[name="${e}"]`);n&&(l[e]=n.type==="checkbox"?n.checked:n.value)}return l}function g(t,r={},...l){const e=document.createElement(t);Object.assign(e,r);for(const n of l)n!=null&&e.append(n);return e}function I(t,r,l){const e=g("label",{className:"form-field"},g("span",{className:"form-label",textContent:t}),r);return l&&e.append(g("small",{className:"form-hint",textContent:l,style:"display:block;font-size:.78em;opacity:.7;margin-top:.25rem;line-height:1.35"})),e}function Q(t,r){const l=[["","\u2014 Everyone \u2014"],["public","Public \u2014 anyone, incl. logged-out"],...(r||[]).map(n=>[n.name,`${n.label||n.name} and above`]),["private","Private \u2014 super-admin only"]];t&&!l.some(([n])=>n===t)&&l.push([t,`${t} (custom)`]);const e=P(l,t||"");return e.name="visibility",e}function P(t,r){const l=g("select",{className:"form-select"});return t.forEach(([e,n])=>l.add(new Option(n,e))),l.value=r??"",l}function W(t,r){const l=typeof r=="string"&&r.startsWith("#"),e=P([["","\u2014 none \u2014"],["primary","primary"],["success","success"],["danger","danger"],["warning","warning"],["info","info"],["neutral","neutral"],["__custom__","Custom\u2026"]],l?"__custom__":r||""),n=g("input",{type:"color",className:"form-input me-colour-hex",value:l?r:"#000000"});n.style.display=l?"":"none",e.addEventListener("change",()=>{n.style.display=e.value==="__custom__"?"":"none"});const i=I(t,e);return i.append(n),i._read=()=>e.value==="__custom__"?n.value:e.value,i}function Y(t,r){const l=g("input",{className:"form-input me-icon",name:"icon",value:r||""}),e=g("span",{className:"me-icon-preview"});r&&e.setAttribute("data-icon",r);const n=g("button",{type:"button",className:"btn btn-sm btn-ghost",textContent:"Browse\u2026"}),i=b=>{b?e.setAttribute("data-icon",b):e.removeAttribute("data-icon"),Domma.icons.scan(e.parentNode||e)};return n.addEventListener("click",()=>V(b=>{l.value=b,i(b)})),l.addEventListener("input",()=>i(l.value.trim())),I(t,g("div",{className:"me-icon-field"},l,n,e))}function M(...t){return g("div",{className:"me-field-grid"},...t)}async function Z(t,r,l,e,n){const i=m=>g("h4",{className:"me-form-section",textContent:m}),b=I("Label",g("input",{className:"form-input",name:"text",value:t.text||""}),"The text shown for this item."),y=I("URL",g("input",{className:"form-input",name:"url",value:t.url||""}),"Where it links. Must start with /, #, https://, or mailto: . Leave blank for a parent that only groups sub-items."),w=Y("Icon",t.icon||""),x=I("Visibility",Q(t.visibility||"",e),"Who can see this item, by role seniority. Picking a role shows it to that role and anyone more senior. Blank = everyone."),z=P([["","\u2014 no permission gate \u2014"],...(r||[]).map(m=>[m.key,m.label||m.key])],t.permission||"");z.name="permission";const T=I("Permission",z,"Hide unless the viewer holds this specific permission. Independent of Visibility \u2014 both must pass."),B=I("Badge text",g("input",{className:"form-input",name:"badgeText",value:t.badge?.text||""})),h=P([["","\u2014 none \u2014"],...(l||[]).map(m=>[m.slug,m.title||m.slug])],t.badge?.countFrom||"");h.name="badgeCount";const o=I("Badge count from",h,"Show a live count of this collection\u2019s entries as the badge."),u=W("Badge colour",t.badge?.variant||""),_=P([["link","Link"],["pill","Pill"]],t.pill?"pill":"link");_.name="renderAs";const j=I("Render as",_,"Pill draws the item as a rounded chip. Pill styling applies to top-level navbar items."),C=I("Pill style",P([["filled","Filled"],["outline","Outline"]],t.pill?.style||"filled"));C.querySelector("select").name="pillStyle";const L=W("Pill colour",t.pill?.variant||""),F=g("div",{className:"me-pill-wrap"},M(C,L));F.style.display=t.pill?"":"none",_.addEventListener("change",()=>{F.style.display=_.value==="pill"?"":"none"});const s=W("Text colour",t.colour||""),a=g("button",{className:"btn btn-primary",textContent:"Apply"}),d=g("div",{className:"me-item-form"},i("Link"),b,y,w,i("Access"),M(x,T),i("Badge"),M(B,u),o,i("Appearance"),j,F,s,a),p=E.slideover({title:"Edit menu item",size:"lg",position:"right"});p.element.appendChild(d),p.open();const f=p.element.closest(".dm-slideover")||p.element.querySelector(".dm-slideover")||p.element;f.style.setProperty("width",K+"px","important"),f.style.setProperty("max-width","95vw","important"),Domma.icons.scan(d);const c=m=>d.querySelector(`[name="${m}"]`)?.value||"";a.addEventListener("click",()=>{t.text=c("text"),t.url=c("url"),t.icon=c("icon"),t.visibility=c("visibility"),t.permission=c("permission");const m=c("badgeText"),A=c("badgeCount"),R=u._read();if(t.badge=m||A?{...m&&{text:m},...A&&{countFrom:A},...R&&{variant:R}}:null,c("renderAs")==="pill"){const D=L._read();t.pill={style:c("pillStyle")||"filled",...D&&{variant:D}}}else t.pill=null;t.colour=s._read(),p.close(),n()})}export const menuEditorView={templateUrl:"/admin/js/templates/menu-editor.html",async onMount(t){const r=location.hash.match(/^#\/menus\/edit\/(.+)$/),l=r?decodeURIComponent(r[1]):null,e=!l,n=E.loader(t.get(0),{type:"dots"});let i,b,y,w,x;try{if(e)i={slug:"",name:"",description:"",items:[]};else try{i=await k.menus.get(l)}catch(s){E.toast(`Failed to load menu: ${s.message||s}`,{type:"error"}),location.hash="#/menus";return}b=await k.projects.list().catch(()=>[]),y=await H.get("/api/auth/permissions-registry").catch(()=>({resources:[]})),w=await k.collections.list().catch(()=>[]),x=await k.get("/collections/roles/entries?limit=100").catch(()=>null)}finally{n.destroy()}const T=(Array.isArray(x)?x:x?.entries||[]).map(s=>({name:s.data?.name,label:s.data?.label,level:s.data?.level??99})).filter(s=>s.name).sort((s,a)=>a.level-s.level),B='<option value="">\u2014 no permission gate \u2014</option>',h=s=>B+(y.resources||[]).map(a=>{const d=(s||"")===a.key?" selected":"";return`<option value="${a.key}"${d}>${a.label||a.key}</option>`}).join("");t.find("#me-title").text(e?"New menu":`Edit "${i.slug}"`);let o=q(i.items);const u=t.find("#me-items-list");v(u,o,h),t.find("#me-add-item").on("click",()=>{S(u,o),o.push({id:N(),parentId:null,text:"",url:"/",icon:"",visibility:"",permission:"",hidden:!1,bundled:!1}),v(u,o,h)}),t.find("#me-add-separator").on("click",()=>{S(u,o),o.push({id:N(),parentId:null,type:"separator"}),v(u,o,h)});function _(s){return function(){S(u,o);const a=$(this).closest(".menu-tree-row").data("id"),d=o.findIndex(c=>c.id===a);if(d===-1)return;const p=o[d],f=s==="separator"?{id:N(),parentId:p.parentId,type:"separator"}:{id:N(),parentId:p.parentId,text:"",url:"/",icon:"",visibility:"",permission:"",hidden:!1,bundled:!1};o.splice(d+1,0,f),v(u,o,h)}}t.off("click",".me-ins-item").on("click",".me-ins-item",_("item")),t.off("click",".me-ins-sep").on("click",".me-ins-sep",_("separator")),t.off("click",".me-remove").on("click",".me-remove",function(){const s=$(this).closest(".menu-tree-row").data("id");o=o.filter(a=>a.id!==s&&a.parentId!==s),v(u,o,h)}),t.off("click",".me-indent").on("click",".me-indent",function(){S(u,o);const s=$(this).closest(".menu-tree-row").data("id"),a=o.find(f=>f.id===s);if(!a)return;const d=o.filter(f=>f.parentId===a.parentId),p=d.findIndex(f=>f.id===s);p<=0||d[p-1].type!=="separator"&&(a.parentId=d[p-1].id,v(u,o,h))}),t.off("click",".me-outdent").on("click",".me-outdent",function(){S(u,o);const s=$(this).closest(".menu-tree-row").data("id"),a=o.find(p=>p.id===s);if(!a||a.parentId==null)return;const d=o.find(p=>p.id===a.parentId);a.parentId=d?d.parentId:null,v(u,o,h)}),t.off("click",".me-up").on("click",".me-up",function(){S(u,o);const s=$(this).closest(".menu-tree-row").data("id"),a=o.filter(c=>c.parentId===o.find(m=>m.id===s)?.parentId),d=a.findIndex(c=>c.id===s);if(d<=0)return;const p=o.indexOf(a[d]),f=o.indexOf(a[d-1]);[o[p],o[f]]=[o[f],o[p]],v(u,o,h)}),t.off("click",".me-down").on("click",".me-down",function(){S(u,o);const s=$(this).closest(".menu-tree-row").data("id"),a=o.filter(c=>c.parentId===o.find(m=>m.id===s)?.parentId),d=a.findIndex(c=>c.id===s);if(d===-1||d>=a.length-1)return;const p=o.indexOf(a[d]),f=o.indexOf(a[d+1]);[o[p],o[f]]=[o[f],o[p]],v(u,o,h)}),t.off("click",".me-hidden").on("click",".me-hidden",function(){S(u,o);const s=$(this).closest(".menu-tree-row").data("id"),a=o.find(d=>d.id===s);a&&(a.hidden=!a.hidden),v(u,o,h)}),t.off("click",".me-edit").on("click",".me-edit",function(){const s=$(this).closest(".menu-tree-row").data("id"),a=o.find(d=>d.id===s);!a||a.type==="separator"||Z(a,y.resources||[],w,T,()=>v(u,o,h))}),E.tabs(t.find("#me-tabs").get(0)),t.find("#me-variant").val(i.variant||""),t.find("#me-position").val(i.position||"");const j=t.find("#me-appearOnHover").get(0);j&&(j.checked=i.appearOnHover===!0),t.find("#me-fontFamily").val(i.style?.fontFamily||""),t.find("#me-fontSize").val(i.style?.fontSize||""),t.find("#me-fontWeight").val(i.style?.fontWeight||""),t.find("#me-letterSpacing").val(i.style?.letterSpacing||""),t.find("#me-iconSize").val(i.style?.iconSize||"");const{getProjectFromHash:C}=await import("../lib/project-context.js"),L=e?C():null,F=t.find("#me-project");F.html('<option value="">\u2014 none \u2014</option>'+b.map(s=>`<option value="${O(s.slug)}">${O(s.name||s.slug)}</option>`).join("")),t.find("#me-slug").val(i.slug||""),t.find("#me-name").val(i.name||""),t.find("#me-description").val(i.description||""),F.val(i.meta?.project||L||""),Domma.icons.scan(t.get(0)),t.find("#me-save").on("click",async()=>{S(u,o);const s={variant:t.find("#me-variant").val(),position:t.find("#me-position").val(),appearOnHover:!!(t.find("#me-appearOnHover").get(0)||{}).checked,fontFamily:t.find("#me-fontFamily").val(),fontSize:t.find("#me-fontSize").val(),fontWeight:t.find("#me-fontWeight").val(),letterSpacing:t.find("#me-letterSpacing").val(),iconSize:t.find("#me-iconSize").val()},a={slug:t.find("#me-slug").val().trim(),name:t.find("#me-name").val().trim(),description:t.find("#me-description").val().trim(),project:t.find("#me-project").val()},d=["fontFamily","fontSize","fontWeight","letterSpacing","iconSize"],p=Object.fromEntries(Object.entries(s).filter(([c,m])=>d.includes(c)&&m)),f={slug:a.slug,name:a.name,description:a.description,...s.variant&&{variant:s.variant},...s.position&&{position:s.position},...s.appearOnHover&&{appearOnHover:!0},...Object.keys(p).length&&{style:p},items:X(o),meta:{...i.meta||{},project:a.project||null}};try{if(e)await k.menus.create(f);else if(f.slug!==i.slug){await k.menus.create(f);const c=await k.menuLocations.get();let m=!1;for(const[A,R]of Object.entries(c))R===i.slug&&(c[A]=f.slug,m=!0);m&&await k.menuLocations.save(c),await k.menus.remove(i.slug)}else await k.menus.update(i.slug,f);E.toast("Saved.",{type:"success"}),location.hash="#/menus/edit/"+encodeURIComponent(f.slug)}catch(c){E.toast(`Save failed: ${c.message||c}`,{type:"error"})}})}};
|
package/admin/js/views/menus.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import{api as o}from"../api.js";import{filterByProject as b,getProjectFromHash as y}from"../lib/project-context.js";import{keepTableIconsAlive as f}from"../lib/table-icons.js";function l(
|
|
1
|
+
import{api as o}from"../api.js";import{filterByProject as b,getProjectFromHash as y}from"../lib/project-context.js";import{keepTableIconsAlive as f}from"../lib/table-icons.js";function l(s){return String(s).replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""").replace(/'/g,"'")}export const menusView={templateUrl:"/admin/js/templates/menus.html",async onMount(s){const p=E.loader(s.get(0),{type:"dots"});let c,r;try{[c,r]=await Promise.all([o.menus.list().catch(()=>[]),o.menuLocations.get().catch(()=>({}))])}finally{p.destroy()}const d=y(),m=d?b(c,d):c,n={};for(const[e,t]of Object.entries(r||{}))t&&(n[t]||(n[t]=[]),n[t].push(e));T.create("#menus-table",{data:m,emptyMessage:'No menus yet. Click "New menu" to create one.',columns:[{key:"slug",title:"Slug",sortable:!0,render:e=>`<code>${l(e)}</code>`},{key:"name",title:"Name",sortable:!0,render:(e,t)=>`<a href="#/menus/edit/${encodeURIComponent(t.slug)}" style="font-weight:600;">${l(e||t.slug)}</a>`},{key:"itemsCount",title:"Items",render:(e,t)=>String(t.itemsCount??(Array.isArray(t.items)?t.items.length:0))},{key:"mapped",title:"Mapped",render:(e,t)=>{const a=n[t.slug]||[];return a.length?a.map(i=>`<span class="badge badge-info">${l(i)}</span>`).join(" "):'<span class="text-muted">\u2014</span>'}},{key:"bundled",title:"Bundled",render:(e,t)=>t.meta?.bundled?'<span class="badge badge-secondary">bundled</span>':""},{key:"_actions",title:"Actions",render:(e,t)=>{const a=(n[t.slug]||[]).length>0,i=a?'data-tooltip="Unmap this menu before deleting"':"",g=a?"disabled":"",u=l(t.slug);return`
|
|
2
2
|
<a class="btn btn-sm btn-ghost" href="#/menus/edit/${encodeURIComponent(t.slug)}" data-tooltip="Edit"><span data-icon="edit" data-icon-size="14"></span></a>
|
|
3
3
|
<button class="btn btn-sm btn-ghost btn-duplicate" data-slug="${u}" data-tooltip="Duplicate"><span data-icon="copy" data-icon-size="14"></span></button>
|
|
4
|
-
<button class="btn btn-sm btn-
|
|
5
|
-
`}}]}),Domma.icons.scan("#menus-table"),f(
|
|
4
|
+
<button class="btn btn-sm btn-ghost btn-delete" data-slug="${u}" ${g} ${i}><span data-icon="trash" data-icon-size="14"></span></button>
|
|
5
|
+
`}}]}),Domma.icons.scan("#menus-table"),f(s.get(0)),document.querySelectorAll("#menus-table [data-tooltip]").forEach(e=>{E.tooltip(e,{content:e.getAttribute("data-tooltip"),position:"top"})}),s.off("click",".btn-duplicate").on("click",".btn-duplicate",async function(){const e=$(this).data("slug");try{const t=await o.menus.duplicate(e);E.toast(`Duplicated as "${t.slug}".`,{type:"success"}),location.reload()}catch(t){E.toast(`Duplicate failed: ${t.message||t}`,{type:"error"})}}),s.off("click",".btn-delete").on("click",".btn-delete",async function(){if(this.disabled)return;const e=$(this).data("slug");if(await E.confirm(`Delete menu "${e}"? This cannot be undone.`))try{await o.menus.remove(e),E.toast("Menu deleted.",{type:"success"}),location.reload()}catch(a){E.toast(`Delete failed: ${a.message||a}`,{type:"error"})}})}};
|
|
@@ -8,7 +8,7 @@ import{api as x}from"../api.js";let k=1;const v=n=>String(n||"").replace(/&/g,"&
|
|
|
8
8
|
<span class="nav-col-action">
|
|
9
9
|
<button class="btn btn-sm btn-ghost btn-toggle-bundled${t.bundled?" active":""}" data-id="${t._id}" data-bundled="${!!t.bundled}" data-tooltip="${t.bundled?"Unbundle":"Bundle (seed on install)"}"><span data-icon="package"></span></button>
|
|
10
10
|
<button class="btn btn-sm btn-ghost btn-toggle-hidden${c?" active":""}" data-id="${t._id}" data-hidden="${c}" data-tooltip="${c?"Show":"Hide"}"><span data-icon="eye-off"></span></button>
|
|
11
|
-
<button class="btn btn-sm btn-
|
|
11
|
+
<button class="btn btn-sm btn-ghost btn-remove-item" data-id="${t._id}" data-tooltip="Remove"><span data-icon="trash"></span></button>
|
|
12
12
|
</span>
|
|
13
13
|
</div>
|
|
14
14
|
`)}),Domma.icons.scan("#nav-items-list"),document.querySelectorAll("#nav-items-list [data-tooltip]").forEach(t=>{E.tooltip(t,{content:t.getAttribute("data-tooltip"),position:"top"})})},m=()=>{const d=n.find("#footer-links-list").empty();e.forEach((i,t)=>{const s=i.hidden===!0;d.append(`
|
|
@@ -17,7 +17,7 @@ import{api as x}from"../api.js";let k=1;const v=n=>String(n||"").replace(/&/g,"&
|
|
|
17
17
|
<input type="text" class="form-input footer-link-url nav-col-main" value="${v(i.url)}" placeholder="/url">
|
|
18
18
|
<span class="nav-col-action">
|
|
19
19
|
<button class="btn btn-sm btn-ghost btn-toggle-hidden${s?" active":""}" data-idx="${t}" data-hidden="${s}" data-tooltip="${s?"Show":"Hide"}"><span data-icon="eye-off"></span></button>
|
|
20
|
-
<button class="btn btn-sm btn-
|
|
20
|
+
<button class="btn btn-sm btn-ghost btn-remove-footer" data-idx="${t}" data-tooltip="Remove"><span data-icon="trash"></span></button>
|
|
21
21
|
</span>
|
|
22
22
|
</div>
|
|
23
23
|
`)}),Domma.icons.scan("#footer-links-list"),document.querySelectorAll("#footer-links-list [data-tooltip]").forEach(i=>{E.tooltip(i,{content:i.getAttribute("data-tooltip"),position:"top"})})},h=()=>{e=[],n.find("#footer-links-list .nav-item-row").each(function(){const d=$(this).find(".btn-toggle-hidden").attr("data-hidden")==="true",i={text:$(this).find(".footer-link-text").val().trim(),url:$(this).find(".footer-link-url").val().trim()};d&&(i.hidden=!0),e.push(i)})};n.find("#add-footer-link").on("click",()=>{h(),e.push({text:"",url:"",hidden:!1}),m()}),n.off("click",".btn-remove-footer").on("click",".btn-remove-footer",function(){h();const d=parseInt($(this).data("idx"),10);e.splice(d,1),m()}),n.find("#field-brand-text").val(a.brand?.text||""),n.find("#field-brand-url").val(a.brand?.url||"/"),n.find("#field-brand-icon").val(a.brand?.icon||""),n.find("#field-nav-variant").val(a.variant||"dark"),n.find("#field-brand-tagline").val(a.brand?.tagline||""),n.find("#field-brand-logo").val(a.brand?.logo||""),n.find("#field-nav-position").val(a.position||"sticky"),n.find("#field-brand-size").val(a.brand?.size||"md"),n.find("#field-nav-font-family").val(a.style?.fontFamily||""),n.find("#field-nav-font-size").val(a.style?.fontSize||""),n.find("#field-nav-font-weight").val(a.style?.fontWeight||""),n.find("#field-nav-letter-spacing").val(a.style?.letterSpacing||""),n.find("#field-nav-icon-size").val(a.style?.iconSize||""),n.find("#field-admin-brand-title").val(l.adminBrand?.title||""),n.find("#field-admin-brand-icon").val(l.adminBrand?.icon||""),n.find("#field-admin-brand-logo").val(l.adminBrand?.logo||""),p(),m(),E.tabs(n.find("#nav-tabs").get(0)),n.find("#add-nav-item").on("click",()=>{u(),o.push({_id:k++,text:"",url:"",icon:"",hidden:!1,parentId:null}),p()}),n.off("click",".btn-remove-item").on("click",".btn-remove-item",function(){u();const d=parseInt($(this).data("id"),10);o=o.filter(i=>i._id!==d&&i.parentId!==d),p()}),n.off("click","#nav-items-list .btn-toggle-bundled").on("click","#nav-items-list .btn-toggle-bundled",function(d){d.stopPropagation(),u();const i=parseInt($(this).data("id"),10),t=o.find(s=>s._id===i);t&&(t.bundled=!t.bundled),p()}),n.off("click","#nav-items-list .btn-toggle-hidden").on("click","#nav-items-list .btn-toggle-hidden",function(d){d.stopPropagation(),u();const i=parseInt($(this).data("id"),10),t=o.find(s=>s._id===i);t&&(t.hidden=!t.hidden),p()}),n.off("click","#footer-links-list .btn-toggle-hidden").on("click","#footer-links-list .btn-toggle-hidden",function(d){d.stopPropagation(),h();const i=parseInt($(this).data("idx"),10);e[i]&&(e[i].hidden=!e[i].hidden),m()}),n.off("change",".item-parent").on("change",".item-parent",function(){u(),p()}),n.find("#save-nav-btn").on("click",async()=>{u(),h();const d=B(o.map(r=>({...r,text:r.text.trim(),url:r.url.trim(),icon:r.icon.trim()}))).filter(r=>r.text||r.url),i=n.find("#field-brand-icon").val().trim(),t=n.find("#field-brand-tagline").val().trim(),s=n.find("#field-brand-logo").val().trim(),c=n.find("#field-brand-size").val(),g={brand:{text:n.find("#field-brand-text").val().trim(),url:n.find("#field-brand-url").val().trim()||"/",...s&&{logo:s},...i&&!s&&{icon:i},...t&&{tagline:t},...c!=="md"&&{size:c}},items:d,variant:n.find("#field-nav-variant").val(),position:n.find("#field-nav-position").val()||"sticky",style:{...n.find("#field-nav-font-family").val()&&{fontFamily:n.find("#field-nav-font-family").val()},...n.find("#field-nav-font-size").val()&&{fontSize:n.find("#field-nav-font-size").val()},...n.find("#field-nav-font-weight").val()&&{fontWeight:n.find("#field-nav-font-weight").val()},...n.find("#field-nav-letter-spacing").val()&&{letterSpacing:n.find("#field-nav-letter-spacing").val()},...n.find("#field-nav-icon-size").val()&&{iconSize:n.find("#field-nav-icon-size").val()}}},f=e.filter(r=>r.text||r.url);try{await x.navigation.save(g),a=g,o=S(a.items),p(),E.toast("Navigation saved.",{type:"success"})}catch(r){console.error("[navigation] save failed:",r),E.toast("Failed to save navigation.",{type:"error"});return}const y=n.find("#field-admin-brand-logo").val().trim(),I=n.find("#field-admin-brand-icon").val().trim(),_=n.find("#field-admin-brand-title").val().trim(),b={..._&&{title:_},...y&&{logo:y},...!y&&I&&{icon:I}};try{await x.settings.save({adminBrand:b,footer:{links:f}}),l={...l,adminBrand:b},e=f,m();const r=b.title||"CMS Admin",A=b.icon||"layout",w=b.logo||"",z=$("#admin-topbar .topbar-brand");w?z.html(`<img src="${w}" class="topbar-brand-logo" alt="${r}"><span class="topbar-brand-text">${r}</span>`):z.html(`<span data-icon="${A}"></span><span class="topbar-brand-text">${r}</span>`),Domma.icons.scan("#admin-topbar .topbar-brand")}catch(r){console.error("[navigation] admin brand / footer save failed:",r),E.toast(`Admin brand save failed: ${r.message||r}`,{type:"error"})}})}};
|