domma-cms 0.36.0 → 0.36.2

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.
Files changed (65) hide show
  1. package/admin/css/admin.css +1 -1
  2. package/admin/css/dashboard.css +1 -1
  3. package/admin/js/lib/card-builder.js +2 -2
  4. package/admin/js/lib/crud-tutorial.js +1 -1
  5. package/admin/js/lib/effects-builder.js +2 -2
  6. package/admin/js/lib/embed-builder.js +1 -1
  7. package/admin/js/lib/markdown-toolbar.js +15 -15
  8. package/admin/js/lib/project-quick-create.js +2 -2
  9. package/admin/js/lib/scribe-composer.js +3 -3
  10. package/admin/js/lib/shortcode-context-menu.js +6 -6
  11. package/admin/js/lib/shortcode-modal.js +1 -1
  12. package/admin/js/lib/shortcode-registry.js +1 -1
  13. package/admin/js/lib/shortcode-tags.js +1 -1
  14. package/admin/js/lib/sidebar-renderer.js +2 -2
  15. package/admin/js/lib/timeline-builder.js +2 -2
  16. package/admin/js/templates/api-endpoint-editor.html +2 -2
  17. package/admin/js/templates/block-editor.html +1 -1
  18. package/admin/js/templates/component-editor.html +4 -4
  19. package/admin/js/templates/docs/api-actions.html +11 -11
  20. package/admin/js/templates/docs/api-authentication.html +11 -11
  21. package/admin/js/templates/docs/api-collections.html +11 -11
  22. package/admin/js/templates/docs/api-layouts.html +11 -11
  23. package/admin/js/templates/docs/api-media.html +11 -11
  24. package/admin/js/templates/docs/api-navigation.html +11 -11
  25. package/admin/js/templates/docs/api-pages.html +11 -11
  26. package/admin/js/templates/docs/api-plugins.html +11 -11
  27. package/admin/js/templates/docs/api-settings.html +11 -11
  28. package/admin/js/templates/docs/api-users.html +11 -11
  29. package/admin/js/templates/docs/api-views.html +11 -11
  30. package/admin/js/templates/effects.html +752 -752
  31. package/admin/js/templates/form-editor.html +4 -4
  32. package/admin/js/templates/form-submissions.html +1 -1
  33. package/admin/js/templates/forms.html +17 -17
  34. package/admin/js/templates/my-profile.html +17 -17
  35. package/admin/js/templates/navigation.html +3 -3
  36. package/admin/js/templates/notifications.html +1 -1
  37. package/admin/js/templates/page-editor.html +3 -3
  38. package/admin/js/templates/project-detail.html +19 -19
  39. package/admin/js/templates/role-editor.html +70 -70
  40. package/admin/js/templates/roles.html +10 -10
  41. package/admin/js/templates/settings.html +6 -6
  42. package/admin/js/views/action-editor.js +1 -1
  43. package/admin/js/views/api-endpoint-editor.js +3 -3
  44. package/admin/js/views/collection-editor.js +2 -2
  45. package/admin/js/views/collection-entries.js +1 -1
  46. package/admin/js/views/component-editor.js +3 -3
  47. package/admin/js/views/form-editor.js +3 -3
  48. package/admin/js/views/form-submissions.js +1 -1
  49. package/admin/js/views/page-editor.js +4 -4
  50. package/admin/js/views/project-detail.js +1 -1
  51. package/admin/js/views/settings.js +2 -2
  52. package/bin/lib/config-merge.js +44 -44
  53. package/package.json +1 -1
  54. package/public/css/forms.css +1 -1
  55. package/public/css/site.css +1 -1
  56. package/public/js/menu-decor.mjs +1 -1
  57. package/public/js/site.js +1 -1
  58. package/server/middleware/auth.js +253 -253
  59. package/server/routes/api/auth.js +309 -309
  60. package/server/routes/api/navigation.js +42 -42
  61. package/server/routes/api/settings.js +141 -141
  62. package/server/services/email.js +167 -167
  63. package/server/services/userProfiles.js +199 -199
  64. package/server/services/users.js +302 -302
  65. package/config/connections.json.bak +0 -9
@@ -1,3 +1,3 @@
1
- import{api as _}from"../api.js";const de=[["_eq","equals"],["_ne","not equal"],["_gt",">"],["_gte",">="],["_lt","<"],["_lte","<="],["_in","in list"],["_nin","not in list"],["_contains","contains"],["_starts","starts with"],["_ends","ends with"],["_exists","exists"]],xe=["id","createdBy"],we=/\{\{\s*(params|query)\.([a-zA-Z_][a-zA-Z0-9_]*)\s*\}\}/g;function Se(C){const b=new Set;for(const n of Object.values(C?.filter||{}))if(typeof n=="string")for(const N of n.matchAll(we))N[1]==="query"&&b.add(N[2]);return[...b]}function je(C){return String(C??"").replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")}let O=null;function qe(C){for(const[b]of de)if(C.endsWith(b))return{field:C.slice(0,-b.length),op:b};return{field:C,op:"_eq"}}export const apiEndpointEditorView={templateUrl:"/admin/js/templates/api-endpoint-editor.html",async onMount(C){const b=C.get(0),n=e=>b.querySelector("#"+e)||document.getElementById(e),N=location.hash.split("?")[0].match(/\/api-endpoints\/edit\/([^/?#]+)/),q=N?decodeURIComponent(N[1]):null;n("ep-title").textContent=q?"Edit API Endpoint":"New API Endpoint",E.tabs(n("ep-editor-tabs"));const[pe,ue,me,u]=await Promise.all([_.projects.list().catch(()=>[]),_.collections.list().catch(()=>[]),_.get("/collections/roles/entries?limit=100").catch(()=>({entries:[]})),q?_.apiEndpoints.get(q).catch(()=>null):Promise.resolve(null)]);if(q&&!u){E.toast("Endpoint not found.",{type:"error"}),location.hash="#/api-endpoints";return}const fe=ue.filter(e=>!e.systemManaged&&!e.preset),ye=(me.entries||[]).map(e=>e.data?.name).filter(Boolean),k=n("endpoint-project");for(const e of pe){const t=document.createElement("option");t.value=e.slug,t.textContent=`${e.name} (${e.slug})`,k.appendChild(t)}const y=n("ep-collection");function G(e){return e.meta&&e.meta.project||"core"}function z(e){const t=e!==void 0?e:y.value,r=k.value,o=fe.filter(a=>{const s=G(a);return s===r||s==="core"});y.textContent="";for(const a of o){const s=document.createElement("option");s.value=a.slug,s.textContent=`${a.title} (${a.slug})${G(a)==="core"&&r!=="core"?" \u2014 core":""}`,y.appendChild(s)}if(!o.length){const a=document.createElement("option");a.value="",a.textContent="No collections available in this project",y.appendChild(a)}t&&[...y.options].some(a=>a.value===t)&&(y.value=t)}z();const L=n("ep-auth");for(const[e,t]of[["public","Public \u2014 no credentials"],["token","Token \u2014 project API token"]]){const r=document.createElement("option");r.value=e,r.textContent=t,L.appendChild(r)}for(const e of ye){const t=document.createElement("option");t.value=e,t.textContent=`Role: ${e}`,L.appendChild(t)}let T=[];async function R(){const e=y.value;if(T=[],e)try{T=((await _.collections.get(e))?.fields||[]).map(r=>r.name).filter(Boolean)}catch{}ee();for(const t of K.querySelectorAll(".ep-filter-row"))U(t.querySelector(".ep-f-field"));te()}function F(){return[...T,...xe]}function ee(e=[]){const t=n("ep-fields-list");t.textContent="";for(const r of T){const o=document.createElement("label");o.style.cssText="display:flex;align-items:center;gap:.35rem;cursor:pointer;font-size:.9rem;";const a=document.createElement("input");a.type="checkbox",a.className="ep-field-cb",a.value=r,a.checked=e.includes(r),a.addEventListener("change",h),o.appendChild(a),o.appendChild(document.createTextNode(r)),t.appendChild(o)}if(!T.length){const r=document.createElement("p");r.className="text-muted",r.style.cssText="font-size:.85rem;margin:0;",r.textContent="No fields found on this collection.",t.appendChild(r)}}function te(e){const t=n("ep-sort"),r=e!==void 0?e:t.value;t.textContent="";for(const o of["createdAt",...F()]){const a=document.createElement("option");a.value=o==="createdAt"?"":o,a.textContent=o==="createdAt"?"createdAt (default)":o,t.appendChild(a)}t.value=r||""}function U(e,t){const r=t!==void 0?t:e.value;e.textContent="";for(const a of F()){const s=document.createElement("option");s.value=a,s.textContent=a,e.appendChild(s)}const o=document.createElement("option");if(o.value="__custom__",o.textContent="custom dot-path\u2026",e.appendChild(o),r&&[...e.options].some(a=>a.value===r))e.value=r;else if(r){const a=document.createElement("option");a.value=r,a.textContent=r,e.insertBefore(a,o),e.value=r}}const K=n("ep-filter-rows");function M(e="",t="_eq",r=""){const o=document.createElement("div");o.className="ep-filter-row",o.style.cssText="display:grid;grid-template-columns:2fr 1.2fr 2fr auto;gap:.5rem;margin-bottom:.5rem;align-items:center;";const a=document.createElement("select");a.className="form-input ep-f-field",U(a,e||void 0),a.addEventListener("change",()=>{if(a.value==="__custom__"){const i=prompt("Dot-path field (e.g. address.city):","");i&&i.trim()?U(a,i.trim()):a.value=F()[0]||""}h()});const s=document.createElement("select");s.className="form-input ep-f-op";for(const[i,j]of de){const w=document.createElement("option");w.value=i,w.textContent=j,s.appendChild(w)}s.value=t,s.addEventListener("change",h);const c=document.createElement("input");c.type="text",c.className="form-input ep-f-value",c.placeholder="e.g. {{params.date}} or {{query.team}} or a literal",c.value=r,c.addEventListener("input",h);const d=document.createElement("button");d.className="btn btn-sm btn-ghost",d.innerHTML='<span data-icon="trash"></span>',d.addEventListener("click",()=>{o.remove(),h()}),o.appendChild(a),o.appendChild(s),o.appendChild(c),o.appendChild(d),K.appendChild(o),Domma.icons.scan(o)}function he(){const e={};for(const t of K.querySelectorAll(".ep-filter-row")){const r=t.querySelector(".ep-f-field").value,o=t.querySelector(".ep-f-op").value,a=t.querySelector(".ep-f-value").value;!r||r==="__custom__"||(e[o==="_eq"?r:r+o]=a)}return e}function I(){return{name:n("ep-name").value.trim(),project:k.value,path:n("ep-path").value.trim(),collection:y.value,auth:L.value,mode:n("ep-mode").value,filter:he(),sort:n("ep-sort").value||null,order:n("ep-order").value,limit:parseInt(n("ep-limit").value,10)||0,fields:[...b.querySelectorAll(".ep-field-cb:checked")].map(e=>e.value),enabled:n("ep-enabled").checked}}let B=null,v=null;function D(){return!B||JSON.stringify(I())!==B}function h(){ne(),Z()}function ne(){n("ep-url-preview").textContent=`/api/x/${k.value||"<project>"}${n("ep-path").value||"/\u2026"}`}function ve(e){return(e.match(/:([a-zA-Z_][a-zA-Z0-9_]*)/g)||[]).map(t=>t.slice(1))}function Z(){const e=D();n("tryit-dirty-note").style.display=e?"":"none",n("tryit-send").disabled=e,n("tryit-token-group").style.display=v?.auth==="token"?"":"none";const t=n("tryit-params"),r={};for(const s of t.querySelectorAll("input"))r[s.dataset.param]=s.value;t.textContent="";for(const s of ve(v?.path||"")){const c=document.createElement("div");c.className="form-group";const d=document.createElement("label");d.className="form-label",d.textContent=`:${s}`;const i=document.createElement("input");i.type="text",i.className="form-input",i.dataset.param=s,i.value=r[s]||"",c.appendChild(d),c.appendChild(i),t.appendChild(c)}const o=n("tryit-query-params"),a={};for(const s of o.querySelectorAll("input"))a[s.dataset.queryParam]=s.value;o.textContent="";for(const s of Se(v)){const c=document.createElement("div");c.className="form-group";const d=document.createElement("label");d.className="form-label",d.textContent=`?${s}`;const i=document.createElement("input");i.type="text",i.className="form-input",i.dataset.queryParam=s,i.value=a[s]||"",i.placeholder=`optional \u2014 filters by ${s}`,c.appendChild(d),c.appendChild(i),o.appendChild(c)}}let J="";const W=`apitryit:hist:${q||"new"}`,ge=12;function oe(){const e=S.get(W);return Array.isArray(e)?e:[]}function ae(e){const t=oe().filter(o=>o.url!==e.url),r=[e,...t].slice(0,ge);S.set(W,r),Y()}function Ee(e){for(const t of n("tryit-params").querySelectorAll("input"))t.value=e.params?.[t.dataset.param]??"";for(const t of n("tryit-query-params").querySelectorAll("input"))t.value=e.queryParams?.[t.dataset.queryParam]??"";n("tryit-query").value=e.extra||"",Q()}function Y(){const e=n("tryit-history-list"),t=n("tryit-history-card");if(!e||!t)return;const r=oe();t.style.display=r.length?"":"none",e.textContent="";for(const o of r){const a=document.createElement("button");a.type="button",a.className="btn btn-ghost btn-sm",a.style.cssText="display:flex;align-items:center;gap:.5rem;width:100%;justify-content:flex-start;text-align:left;font-family:var(--font-mono,monospace);font-size:.78rem;padding:.35rem .5rem;";const s=document.createElement("span"),c=o.ok;s.className="badge "+(c?"badge-success":o.status>=500||o.status===0?"badge-danger":"badge-warning"),s.style.cssText="flex:0 0 auto;",s.textContent=o.status||"ERR";const d=document.createElement("span");d.style.cssText="flex:1 1 auto;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;",d.textContent=o.url.replace(`/api/x/${v?.project||""}`,"")||o.url,d.title=o.url,a.appendChild(s),a.appendChild(d),a.addEventListener("click",()=>Ee(o)),e.appendChild(a)}}const m={key:"#4098d7",index:"#9aa4b2",string:"#3f9d63",number:"#c0803a",boolean:"#9a6dce",nul:"#c05b52",punc:"rgba(127,127,127,.8)",preview:"#9aa4b2"};function f(e,t,r){const o=document.createElement("span");return o.style.color=e,r&&(o.style.cssText+=";"+r),o.textContent=t,o}function re(e,t){return t?f(m.index,String(e)):f(m.key,JSON.stringify(String(e)))}function Ce(e){return e===null?f(m.nul,"null"):typeof e=="string"?f(m.string,JSON.stringify(e)):typeof e=="number"?f(m.number,String(e)):typeof e=="boolean"?f(m.boolean,String(e)):f(m.string,JSON.stringify(e))}function se(e,t,r){const o=e&&typeof e=="object",a=o&&(Array.isArray(e)?e.length===0:Object.keys(e).length===0);if(!o||a){const p=document.createElement("div");return p.style.cssText="white-space:pre-wrap;word-break:break-word;padding-left:1.2em;",t!==void 0&&(p.appendChild(re(t,r)),p.appendChild(f(m.punc,": "))),p.appendChild(o?f(m.punc,Array.isArray(e)?"[]":"{}"):Ce(e)),p}const s=Array.isArray(e),c=s?e.map((p,x)=>[x,p,!0]):Object.entries(e).map(([p,x])=>[p,x,!1]),d=document.createElement("div"),i=document.createElement("div");i.style.cssText="white-space:pre-wrap;cursor:pointer;";const j=f(m.punc,"\u25BE","display:inline-block;width:1.2em;");i.appendChild(j),t!==void 0&&(i.appendChild(re(t,r)),i.appendChild(f(m.punc,": "))),i.appendChild(f(m.punc,s?"[":"{"));const w=f(m.preview,` ${c.length} ${s?"item":"key"}${c.length===1?"":"s"} `,"font-style:italic;"),P=f(m.punc,s?"]":"}");i.appendChild(w),i.appendChild(P),d.appendChild(i);const A=document.createElement("div");A.style.cssText="padding-left:1.2em;border-left:1px solid rgba(127,127,127,.18);";for(const[p,x,X]of c)A.appendChild(se(x,p,X));d.appendChild(A);const $=document.createElement("div");$.style.paddingLeft="1.2em",$.appendChild(f(m.punc,s?"]":"}")),d.appendChild($);let l=!1;const g=p=>{l=p,j.textContent=p?"\u25B8":"\u25BE",w.style.display=p?"":"none",P.style.display=p?"":"none",A.style.display=p?"none":"",$.style.display=p?"none":""};return g(!1),i.addEventListener("click",()=>g(!l)),d}let V="";function ie(e){n("tryit-json").style.display=e?"":"none",n("tryit-body").style.display=e?"none":"",n("tryit-json-toggle").textContent=e?"Raw":"Tree"}function le(e){V=e;const t=n("tryit-results-placeholder");t&&(t.style.display="none"),n("tryit-response-card").style.display="";let r,o=!0;try{r=JSON.parse(e)}catch{o=!1}n("tryit-body").textContent=o?JSON.stringify(r,null,2):e;const a=n("tryit-json");a.textContent="",o&&a.appendChild(se(r)),n("tryit-json-toggle").style.display=o?"":"none",ie(o)}async function Q(){if(!v)return;const e={},t={};let r=v.path;for(const l of n("tryit-params").querySelectorAll("input")){e[l.dataset.param]=l.value;const g=new RegExp(":"+l.dataset.param.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")+"(?=/|$)","g");r=r.replace(g,encodeURIComponent(l.value))}const o=[];for(const l of n("tryit-query-params").querySelectorAll("input")){const g=l.value.trim();t[l.dataset.queryParam]=l.value,g&&o.push(`${encodeURIComponent(l.dataset.queryParam)}=${encodeURIComponent(g)}`)}const a=n("tryit-query").value.trim();a&&o.push(a);const s=o.join("&"),c=`/api/x/${v.project}${r}${s?"?"+s:""}`,d={ts:Date.now(),url:c,params:e,queryParams:t,extra:a},i={},j=n("tryit-token").value.trim();v.auth==="token"&&j&&(i.Authorization=`Bearer ${j}`);const w=location.origin+c,P=l=>`'${String(l).replace(/'/g,"'\\''")}'`,A=[`curl ${P(w)}`];for(const[l,g]of Object.entries(i))A.push(` -H ${P(`${l}: ${g}`)}`);J=A.join(` \\
2
- `),n("tryit-result").style.display="",n("tryit-curl").textContent=J;const $=n("tryit-send");$.disabled=!0;try{const l=await fetch(c,{headers:i}),g=await l.text(),p=n("tryit-status");p.textContent=`${l.status} ${l.statusText}`,p.className="badge "+(l.ok?"badge-success":l.status>=500?"badge-danger":"badge-warning");const x=[];l.headers.forEach((X,be)=>x.push(`${be}: ${X}`)),n("tryit-headers-group").style.display="",n("tryit-headers").textContent=x.length?x.join(`
3
- `):"(none exposed)",le(g),ae({...d,status:l.status,statusText:l.statusText,ok:l.ok})}catch(l){n("tryit-status").textContent="Network error",n("tryit-status").className="badge badge-danger",n("tryit-headers-group").style.display="none",le(String(l.message||l)),ae({...d,status:0,statusText:"Network error",ok:!1})}finally{$.disabled=D()}}if(u){if(n("ep-name").value=u.name||"",k.value=u.project,k.disabled=!0,n("ep-path").value=u.path||"",z(u.collection),y.value!==u.collection){const e=document.createElement("option");e.value=u.collection,e.textContent=`${u.collection} (other project \u2014 no longer allowed)`,y.appendChild(e),y.value=u.collection}L.value=u.auth||"public",n("ep-mode").value=u.mode||"list",n("ep-order").value=u.order||"desc",n("ep-limit").value=u.limit??50,n("ep-enabled").checked=u.enabled!==!1,await R(),ee(u.fields||[]),te(u.sort||"");for(const[e,t]of Object.entries(u.filter||{})){const{field:r,op:o}=qe(e);M(r,o,String(t))}v=u,B=JSON.stringify(I())}else await R(),M();ne(),Z();for(const e of["ep-name","ep-path","ep-limit"])n(e).addEventListener("input",h);for(const e of["ep-mode","ep-order","ep-sort"])n(e).addEventListener("change",h);n("ep-enabled").addEventListener("change",h),k.addEventListener("change",async()=>{z(),await R(),h()}),L.addEventListener("change",h),y.addEventListener("change",async()=>{await R(),h()}),n("ep-add-filter").addEventListener("click",()=>{M(),h()}),n("tryit-send").addEventListener("click",Q),n("tryit-history-clear").addEventListener("click",()=>{S.set(W,[]),Y()}),n("tryit-json-toggle").addEventListener("click",()=>{ie(n("tryit-json").style.display==="none")}),n("tryit-body-copy").addEventListener("click",async()=>{if(!V){E.toast("Send a request first.",{type:"info"});return}try{await navigator.clipboard.writeText(V),E.toast("Response copied.",{type:"success"})}catch{E.toast("Copy failed.",{type:"error"})}}),n("tryit-copy-curl").addEventListener("click",async()=>{if(!J){E.toast("Send a request first.",{type:"info"});return}try{await navigator.clipboard.writeText(J),E.toast("curl command copied.",{type:"success"})}catch{E.toast("Copy failed.",{type:"error"})}});async function ce(){const e=I();if(!e.name){E.toast("A name is required.",{type:"error"});return}if(!e.path.startsWith("/")){E.toast('The path must start with "/".',{type:"error"});return}try{if(q)v=await _.apiEndpoints.update(q,e),B=JSON.stringify(I()),E.toast("Endpoint saved.",{type:"success"}),Z();else{const t=await _.apiEndpoints.create(e);E.toast("Endpoint created.",{type:"success"}),location.hash="#/api-endpoints/edit/"+encodeURIComponent(t.id)}}catch(t){E.toast(`Save failed: ${t.message||t}`,{type:"error"})}}n("ep-save").addEventListener("click",ce);function H(e){if(!document.getElementById("ep-save")){document.removeEventListener("keydown",H),O===H&&(O=null);return}if(!(e.metaKey||e.ctrlKey))return;const r=e.key.toLowerCase();r==="s"?(e.preventDefault(),ce()):r==="enter"&&(e.preventDefault(),v&&!D()?Q():D()&&E.toast("Save first \u2014 Send tests the saved definition.",{type:"info"}))}O&&document.removeEventListener("keydown",O),O=H,document.addEventListener("keydown",H),Y(),Domma.icons.scan(b)}};
1
+ import{api as _}from"../api.js";const de=[["_eq","equals"],["_ne","not equal"],["_gt",">"],["_gte",">="],["_lt","<"],["_lte","<="],["_in","in list"],["_nin","not in list"],["_contains","contains"],["_starts","starts with"],["_ends","ends with"],["_exists","exists"]],be=["id","createdBy"],we=/\{\{\s*(params|query)\.([a-zA-Z_][a-zA-Z0-9_]*)\s*\}\}/g;function Se(C){const x=new Set;for(const n of Object.values(C?.filter||{}))if(typeof n=="string")for(const N of n.matchAll(we))N[1]==="query"&&x.add(N[2]);return[...x]}function je(C){return String(C??"").replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")}let O=null;function qe(C){for(const[x]of de)if(C.endsWith(x))return{field:C.slice(0,-x.length),op:x};return{field:C,op:"_eq"}}export const apiEndpointEditorView={templateUrl:"/admin/js/templates/api-endpoint-editor.html",async onMount(C){const x=C.get(0),n=e=>x.querySelector("#"+e)||document.getElementById(e),N=location.hash.split("?")[0].match(/\/api-endpoints\/edit\/([^/?#]+)/),q=N?decodeURIComponent(N[1]):null;n("ep-title").textContent=q?"Edit API Endpoint":"New API Endpoint",E.tabs(n("ep-editor-tabs"));const[pe,ue,me,u]=await Promise.all([_.projects.list().catch(()=>[]),_.collections.list().catch(()=>[]),_.get("/collections/roles/entries?limit=100").catch(()=>({entries:[]})),q?_.apiEndpoints.get(q).catch(()=>null):Promise.resolve(null)]);if(q&&!u){E.toast("Endpoint not found.",{type:"error"}),location.hash="#/api-endpoints";return}const fe=ue.filter(e=>!e.systemManaged&&!e.preset),ye=(me.entries||[]).map(e=>e.data?.name).filter(Boolean),k=n("endpoint-project");for(const e of pe){const t=document.createElement("option");t.value=e.slug,t.textContent=`${e.name} (${e.slug})`,k.appendChild(t)}const y=n("ep-collection");function G(e){return e.meta&&e.meta.project||"core"}function z(e){const t=e!==void 0?e:y.value,r=k.value,o=fe.filter(a=>{const s=G(a);return s===r||s==="core"});y.textContent="";for(const a of o){const s=document.createElement("option");s.value=a.slug,s.textContent=`${a.title} (${a.slug})${G(a)==="core"&&r!=="core"?" \u2014 core":""}`,y.appendChild(s)}if(!o.length){const a=document.createElement("option");a.value="",a.textContent="No collections available in this project",y.appendChild(a)}t&&[...y.options].some(a=>a.value===t)&&(y.value=t)}z();const L=n("ep-auth");for(const[e,t]of[["public","Public \u2014 no credentials"],["token","Token \u2014 project API token"]]){const r=document.createElement("option");r.value=e,r.textContent=t,L.appendChild(r)}for(const e of ye){const t=document.createElement("option");t.value=e,t.textContent=`Role: ${e}`,L.appendChild(t)}let T=[];async function R(){const e=y.value;if(T=[],e)try{T=((await _.collections.get(e))?.fields||[]).map(r=>r.name).filter(Boolean)}catch{}ee();for(const t of K.querySelectorAll(".ep-filter-row"))U(t.querySelector(".ep-f-field"));te()}function F(){return[...T,...be]}function ee(e=[]){const t=n("ep-fields-list");t.textContent="";for(const r of T){const o=document.createElement("label");o.style.cssText="display:flex;align-items:center;gap:.35rem;cursor:pointer;font-size:.9rem;";const a=document.createElement("input");a.type="checkbox",a.className="ep-field-cb",a.value=r,a.checked=e.includes(r),a.addEventListener("change",h),o.appendChild(a),o.appendChild(document.createTextNode(r)),t.appendChild(o)}if(!T.length){const r=document.createElement("p");r.className="text-muted",r.style.cssText="font-size:.85rem;margin:0;",r.textContent="No fields found on this collection.",t.appendChild(r)}}function te(e){const t=n("ep-sort"),r=e!==void 0?e:t.value;t.textContent="";for(const o of["createdAt",...F()]){const a=document.createElement("option");a.value=o==="createdAt"?"":o,a.textContent=o==="createdAt"?"createdAt (default)":o,t.appendChild(a)}t.value=r||""}function U(e,t){const r=t!==void 0?t:e.value;e.textContent="";for(const a of F()){const s=document.createElement("option");s.value=a,s.textContent=a,e.appendChild(s)}const o=document.createElement("option");if(o.value="__custom__",o.textContent="custom dot-path\u2026",e.appendChild(o),r&&[...e.options].some(a=>a.value===r))e.value=r;else if(r){const a=document.createElement("option");a.value=r,a.textContent=r,e.insertBefore(a,o),e.value=r}}const K=n("ep-filter-rows");function M(e="",t="_eq",r=""){const o=document.createElement("div");o.className="ep-filter-row",o.style.cssText="display:grid;grid-template-columns:2fr 1.2fr 2fr auto;gap:.5rem;margin-bottom:.5rem;align-items:center;";const a=document.createElement("select");a.className="form-input ep-f-field",U(a,e||void 0),a.addEventListener("change",()=>{if(a.value==="__custom__"){const i=prompt("Dot-path field (e.g. address.city):","");i&&i.trim()?U(a,i.trim()):a.value=F()[0]||""}h()});const s=document.createElement("select");s.className="form-input ep-f-op";for(const[i,j]of de){const w=document.createElement("option");w.value=i,w.textContent=j,s.appendChild(w)}s.value=t,s.addEventListener("change",h);const c=document.createElement("input");c.type="text",c.className="form-input ep-f-value",c.placeholder="e.g. {{params.date}} or {{query.team}} or a literal",c.value=r,c.addEventListener("input",h);const d=document.createElement("button");d.className="btn btn-sm btn-ghost",d.innerHTML='<span data-icon="trash"></span>',d.addEventListener("click",()=>{o.remove(),h()}),o.appendChild(a),o.appendChild(s),o.appendChild(c),o.appendChild(d),K.appendChild(o),Domma.icons.scan(o)}function he(){const e={};for(const t of K.querySelectorAll(".ep-filter-row")){const r=t.querySelector(".ep-f-field").value,o=t.querySelector(".ep-f-op").value,a=t.querySelector(".ep-f-value").value;!r||r==="__custom__"||(e[o==="_eq"?r:r+o]=a)}return e}function I(){return{name:n("ep-name").value.trim(),project:k.value,path:n("ep-path").value.trim(),collection:y.value,auth:L.value,mode:n("ep-mode").value,filter:he(),sort:n("ep-sort").value||null,order:n("ep-order").value,limit:parseInt(n("ep-limit").value,10)||0,fields:[...x.querySelectorAll(".ep-field-cb:checked")].map(e=>e.value),enabled:n("ep-enabled").checked}}let B=null,v=null;function D(){return!B||JSON.stringify(I())!==B}function h(){ne(),Z()}function ne(){n("ep-url-preview").textContent=`/api/x/${k.value||"<project>"}${n("ep-path").value||"/\u2026"}`}function ve(e){return(e.match(/:([a-zA-Z_][a-zA-Z0-9_]*)/g)||[]).map(t=>t.slice(1))}function Z(){const e=D();n("tryit-dirty-note").style.display=e?"":"none",n("tryit-send").disabled=e,n("tryit-token-group").style.display=v?.auth==="token"?"":"none";const t=n("tryit-params"),r={};for(const s of t.querySelectorAll("input"))r[s.dataset.param]=s.value;t.textContent="";for(const s of ve(v?.path||"")){const c=document.createElement("div");c.className="form-group";const d=document.createElement("label");d.className="form-label",d.textContent=`:${s}`;const i=document.createElement("input");i.type="text",i.className="form-input",i.dataset.param=s,i.value=r[s]||"",c.appendChild(d),c.appendChild(i),t.appendChild(c)}const o=n("tryit-query-params"),a={};for(const s of o.querySelectorAll("input"))a[s.dataset.queryParam]=s.value;o.textContent="";for(const s of Se(v)){const c=document.createElement("div");c.className="form-group";const d=document.createElement("label");d.className="form-label",d.textContent=`?${s}`;const i=document.createElement("input");i.type="text",i.className="form-input",i.dataset.queryParam=s,i.value=a[s]||"",i.placeholder=`optional \u2014 filters by ${s}`,c.appendChild(d),c.appendChild(i),o.appendChild(c)}}let J="";const W=`apitryit:hist:${q||"new"}`,ge=12;function oe(){const e=S.get(W);return Array.isArray(e)?e:[]}function ae(e){const t=oe().filter(o=>o.url!==e.url),r=[e,...t].slice(0,ge);S.set(W,r),Y()}function Ee(e){for(const t of n("tryit-params").querySelectorAll("input"))t.value=e.params?.[t.dataset.param]??"";for(const t of n("tryit-query-params").querySelectorAll("input"))t.value=e.queryParams?.[t.dataset.queryParam]??"";n("tryit-query").value=e.extra||"",Q()}function Y(){const e=n("tryit-history-list"),t=n("tryit-history-card");if(!e||!t)return;const r=oe();t.style.display=r.length?"":"none",e.textContent="";for(const o of r){const a=document.createElement("button");a.type="button",a.className="btn btn-ghost btn-sm",a.style.cssText="display:flex;align-items:center;gap:.5rem;width:100%;justify-content:flex-start;text-align:left;font-family:var(--dm-font-mono,monospace);font-size:.78rem;padding:.35rem .5rem;";const s=document.createElement("span"),c=o.ok;s.className="badge "+(c?"badge-success":o.status>=500||o.status===0?"badge-danger":"badge-warning"),s.style.cssText="flex:0 0 auto;",s.textContent=o.status||"ERR";const d=document.createElement("span");d.style.cssText="flex:1 1 auto;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;",d.textContent=o.url.replace(`/api/x/${v?.project||""}`,"")||o.url,d.title=o.url,a.appendChild(s),a.appendChild(d),a.addEventListener("click",()=>Ee(o)),e.appendChild(a)}}const m={key:"var(--dm-info)",index:"var(--dm-text-muted)",string:"var(--dm-success)",number:"var(--dm-warning-dark)",boolean:"var(--dm-secondary)",nul:"var(--dm-danger)",punc:"var(--dm-text-muted)",preview:"var(--dm-text-muted)"};function f(e,t,r){const o=document.createElement("span");return o.style.color=e,r&&(o.style.cssText+=";"+r),o.textContent=t,o}function re(e,t){return t?f(m.index,String(e)):f(m.key,JSON.stringify(String(e)))}function Ce(e){return e===null?f(m.nul,"null"):typeof e=="string"?f(m.string,JSON.stringify(e)):typeof e=="number"?f(m.number,String(e)):typeof e=="boolean"?f(m.boolean,String(e)):f(m.string,JSON.stringify(e))}function se(e,t,r){const o=e&&typeof e=="object",a=o&&(Array.isArray(e)?e.length===0:Object.keys(e).length===0);if(!o||a){const p=document.createElement("div");return p.style.cssText="white-space:pre-wrap;word-break:break-word;padding-left:1.2em;",t!==void 0&&(p.appendChild(re(t,r)),p.appendChild(f(m.punc,": "))),p.appendChild(o?f(m.punc,Array.isArray(e)?"[]":"{}"):Ce(e)),p}const s=Array.isArray(e),c=s?e.map((p,b)=>[b,p,!0]):Object.entries(e).map(([p,b])=>[p,b,!1]),d=document.createElement("div"),i=document.createElement("div");i.style.cssText="white-space:pre-wrap;cursor:pointer;";const j=f(m.punc,"\u25BE","display:inline-block;width:1.2em;");i.appendChild(j),t!==void 0&&(i.appendChild(re(t,r)),i.appendChild(f(m.punc,": "))),i.appendChild(f(m.punc,s?"[":"{"));const w=f(m.preview,` ${c.length} ${s?"item":"key"}${c.length===1?"":"s"} `,"font-style:italic;"),P=f(m.punc,s?"]":"}");i.appendChild(w),i.appendChild(P),d.appendChild(i);const A=document.createElement("div");A.style.cssText="padding-left:1.2em;border-left:1px solid var(--dm-border-light);";for(const[p,b,X]of c)A.appendChild(se(b,p,X));d.appendChild(A);const $=document.createElement("div");$.style.paddingLeft="1.2em",$.appendChild(f(m.punc,s?"]":"}")),d.appendChild($);let l=!1;const g=p=>{l=p,j.textContent=p?"\u25B8":"\u25BE",w.style.display=p?"":"none",P.style.display=p?"":"none",A.style.display=p?"none":"",$.style.display=p?"none":""};return g(!1),i.addEventListener("click",()=>g(!l)),d}let V="";function ie(e){n("tryit-json").style.display=e?"":"none",n("tryit-body").style.display=e?"none":"",n("tryit-json-toggle").textContent=e?"Raw":"Tree"}function le(e){V=e;const t=n("tryit-results-placeholder");t&&(t.style.display="none"),n("tryit-response-card").style.display="";let r,o=!0;try{r=JSON.parse(e)}catch{o=!1}n("tryit-body").textContent=o?JSON.stringify(r,null,2):e;const a=n("tryit-json");a.textContent="",o&&a.appendChild(se(r)),n("tryit-json-toggle").style.display=o?"":"none",ie(o)}async function Q(){if(!v)return;const e={},t={};let r=v.path;for(const l of n("tryit-params").querySelectorAll("input")){e[l.dataset.param]=l.value;const g=new RegExp(":"+l.dataset.param.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")+"(?=/|$)","g");r=r.replace(g,encodeURIComponent(l.value))}const o=[];for(const l of n("tryit-query-params").querySelectorAll("input")){const g=l.value.trim();t[l.dataset.queryParam]=l.value,g&&o.push(`${encodeURIComponent(l.dataset.queryParam)}=${encodeURIComponent(g)}`)}const a=n("tryit-query").value.trim();a&&o.push(a);const s=o.join("&"),c=`/api/x/${v.project}${r}${s?"?"+s:""}`,d={ts:Date.now(),url:c,params:e,queryParams:t,extra:a},i={},j=n("tryit-token").value.trim();v.auth==="token"&&j&&(i.Authorization=`Bearer ${j}`);const w=location.origin+c,P=l=>`'${String(l).replace(/'/g,"'\\''")}'`,A=[`curl ${P(w)}`];for(const[l,g]of Object.entries(i))A.push(` -H ${P(`${l}: ${g}`)}`);J=A.join(` \\
2
+ `),n("tryit-result").style.display="",n("tryit-curl").textContent=J;const $=n("tryit-send");$.disabled=!0;try{const l=await fetch(c,{headers:i}),g=await l.text(),p=n("tryit-status");p.textContent=`${l.status} ${l.statusText}`,p.className="badge "+(l.ok?"badge-success":l.status>=500?"badge-danger":"badge-warning");const b=[];l.headers.forEach((X,xe)=>b.push(`${xe}: ${X}`)),n("tryit-headers-group").style.display="",n("tryit-headers").textContent=b.length?b.join(`
3
+ `):"(none exposed)",le(g),ae({...d,status:l.status,statusText:l.statusText,ok:l.ok})}catch(l){n("tryit-status").textContent="Network error",n("tryit-status").className="badge badge-danger",n("tryit-headers-group").style.display="none",le(String(l.message||l)),ae({...d,status:0,statusText:"Network error",ok:!1})}finally{$.disabled=D()}}if(u){if(n("ep-name").value=u.name||"",k.value=u.project,k.disabled=!0,n("ep-path").value=u.path||"",z(u.collection),y.value!==u.collection){const e=document.createElement("option");e.value=u.collection,e.textContent=`${u.collection} (other project \u2014 no longer allowed)`,y.appendChild(e),y.value=u.collection}L.value=u.auth||"public",n("ep-mode").value=u.mode||"list",n("ep-order").value=u.order||"desc",n("ep-limit").value=u.limit??50,n("ep-enabled").checked=u.enabled!==!1,await R(),ee(u.fields||[]),te(u.sort||"");for(const[e,t]of Object.entries(u.filter||{})){const{field:r,op:o}=qe(e);M(r,o,String(t))}v=u,B=JSON.stringify(I())}else await R(),M();ne(),Z();for(const e of["ep-name","ep-path","ep-limit"])n(e).addEventListener("input",h);for(const e of["ep-mode","ep-order","ep-sort"])n(e).addEventListener("change",h);n("ep-enabled").addEventListener("change",h),k.addEventListener("change",async()=>{z(),await R(),h()}),L.addEventListener("change",h),y.addEventListener("change",async()=>{await R(),h()}),n("ep-add-filter").addEventListener("click",()=>{M(),h()}),n("tryit-send").addEventListener("click",Q),n("tryit-history-clear").addEventListener("click",()=>{S.set(W,[]),Y()}),n("tryit-json-toggle").addEventListener("click",()=>{ie(n("tryit-json").style.display==="none")}),n("tryit-body-copy").addEventListener("click",async()=>{if(!V){E.toast("Send a request first.",{type:"info"});return}try{await navigator.clipboard.writeText(V),E.toast("Response copied.",{type:"success"})}catch{E.toast("Copy failed.",{type:"error"})}}),n("tryit-copy-curl").addEventListener("click",async()=>{if(!J){E.toast("Send a request first.",{type:"info"});return}try{await navigator.clipboard.writeText(J),E.toast("curl command copied.",{type:"success"})}catch{E.toast("Copy failed.",{type:"error"})}});async function ce(){const e=I();if(!e.name){E.toast("A name is required.",{type:"error"});return}if(!e.path.startsWith("/")){E.toast('The path must start with "/".',{type:"error"});return}try{if(q)v=await _.apiEndpoints.update(q,e),B=JSON.stringify(I()),E.toast("Endpoint saved.",{type:"success"}),Z();else{const t=await _.apiEndpoints.create(e);E.toast("Endpoint created.",{type:"success"}),location.hash="#/api-endpoints/edit/"+encodeURIComponent(t.id)}}catch(t){E.toast(`Save failed: ${t.message||t}`,{type:"error"})}}n("ep-save").addEventListener("click",ce);function H(e){if(!document.getElementById("ep-save")){document.removeEventListener("keydown",H),O===H&&(O=null);return}if(!(e.metaKey||e.ctrlKey))return;const r=e.key.toLowerCase();r==="s"?(e.preventDefault(),ce()):r==="enter"&&(e.preventDefault(),v&&!D()?Q():D()&&E.toast("Save first \u2014 Send tests the saved definition.",{type:"info"}))}O&&document.removeEventListener("keydown",O),O=H,document.addEventListener("keydown",H),Y(),Domma.icons.scan(x)}};
@@ -1,5 +1,5 @@
1
1
  import{api as w}from"../api.js";const ee=[{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:"date",label:"Date"},{value:"time",label:"Time"},{value:"url",label:"URL"},{value:"hidden",label:"Hidden field"}],Q=new Set(["select","radio","checkbox-group"]),ae=["public","token","subscriber","editor","manager","admin"],te=["create","read","update","delete"];let g=[],h=null,S=!0,F=null,B="file",X={};function le(e){return e.toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-|-$/g,"")}function ne(e){return ee.find(t=>t.value===e)?.label||e}function oe(e){const t={...g[e]},n=document.getElementById(`fb-label-${e}`),d=document.getElementById(`fb-name-${e}`),s=document.getElementById(`fb-type-${e}`),r=document.getElementById(`fb-required-${e}`),u=document.getElementById(`fb-placeholder-${e}`),l=document.getElementById(`fb-helper-${e}`);if(n&&(t.label=n.value.trim()||t.label),d&&(t.name=d.value.trim()||t.name),s&&(t.type=s.value||t.type),r&&(t.required=r.checked),u&&(t.placeholder=u.value.trim()),l&&(t.helper=l.value.trim()),Q.has(t.type)){const a=document.getElementById(`fb-options-${e}`);a&&(t.options=a.value.split(`
2
- `).filter(c=>c.trim()).map(c=>{const[p,...y]=c.split(":");return{value:p.trim(),label:y.join(":").trim()||p.trim()}}))}const o=document.getElementById(`fb-span-${e}`);if(document.getElementById(`fb-fullwidth-${e}`)?.checked)t.fullWidth=!0,delete t.span;else{delete t.fullWidth;const a=parseInt(o?.value,10);a>1?t.span=a:delete t.span}return t}function Z(){return g.map((e,t)=>oe(t))}function de(e,t){const n=document.createElement("div");n.className="fb-field-card",n.dataset.index=t,n.style.cssText="border:1px solid var(--border-color,#333);border-radius:8px;margin-bottom:.75rem;overflow:hidden;";const d=document.createElement("div");d.className="fb-field-header",d.style.cssText="display:flex;align-items:center;gap:.5rem;padding:.6rem .75rem;background:var(--card-header-bg,rgba(255,255,255,.03));cursor:pointer;user-select:none;";const s=document.createElement("span");s.textContent="\u283F",s.style.cssText="cursor:grab;opacity:.4;font-size:1.1rem;flex-shrink:0;",n.draggable=!0,s.addEventListener("mousedown",()=>{n.draggable=!0}),n.addEventListener("dragstart",i=>{F=t,i.dataTransfer.effectAllowed="move",n.style.opacity="0.4"}),n.addEventListener("dragend",()=>{n.style.opacity="",document.querySelectorAll(".fb-field-card").forEach(i=>i.classList.remove("fb-drag-over"))}),n.addEventListener("dragover",i=>{i.preventDefault(),i.dataTransfer.dropEffect="move",document.querySelectorAll(".fb-field-card").forEach(C=>C.classList.remove("fb-drag-over")),n.classList.add("fb-drag-over")}),n.addEventListener("dragleave",()=>{n.classList.remove("fb-drag-over")}),n.addEventListener("drop",i=>{if(i.preventDefault(),n.classList.remove("fb-drag-over"),F===null||F===t)return;g=Z();const[C]=g.splice(F,1);g.splice(t,0,C),F=null,H(document.getElementById("fields-list"))});const r=document.createElement("span");r.className="fb-field-summary",r.style.cssText="flex:1;font-weight:500;font-size:.9rem;",r.textContent=e.label||"(Untitled field)";const u=document.createElement("span");u.style.cssText="font-size:.75rem;opacity:.5;",u.textContent=ne(e.type);const l=document.createElement("span");l.className="fb-field-chevron",l.textContent="\u25BE",l.style.cssText="opacity:.5;transition:transform .2s;";const o=document.createElement("button");o.type="button",o.innerHTML='<span data-icon="x"></span>',o.className="btn btn-sm btn-ghost",o.style.cssText="padding:.15rem .45rem;line-height:1;opacity:.6;",o.title="Remove field",o.addEventListener("click",i=>{i.stopPropagation(),g.splice(t,1),H(document.getElementById("fields-list"))}),d.appendChild(s),d.appendChild(r),d.appendChild(u),d.appendChild(l),d.appendChild(o),Domma.icons.scan(o);const a=document.createElement("div");a.className="fb-field-body",a.style.cssText="padding:.75rem;display:none;";const c=document.createElement("div");c.style.cssText="display:grid;grid-template-columns:1fr 1fr 1fr;gap:.6rem;margin-bottom:.6rem;";const p=document.createElement("div"),y=document.createElement("label");y.className="form-label",y.textContent="Label";const b=document.createElement("input");b.id=`fb-label-${t}`,b.type="text",b.className="form-input",b.value=e.label||"",b.addEventListener("input",()=>{r.textContent=b.value.trim()||"(Untitled field)";const i=document.getElementById(`fb-name-${t}`);i&&!i.dataset.manual&&(i.value=le(b.value).replace(/-/g,"_"))}),p.appendChild(y),p.appendChild(b);const f=document.createElement("div"),$=document.createElement("label");$.className="form-label",$.textContent="Name (key)";const v=document.createElement("input");v.id=`fb-name-${t}`,v.type="text",v.className="form-input",v.value=e.name||"",v.addEventListener("input",()=>{v.dataset.manual="1"}),f.appendChild($),f.appendChild(v);const T=document.createElement("div"),L=document.createElement("label");L.className="form-label",L.textContent="Type";const m=document.createElement("select");m.id=`fb-type-${t}`,m.className="form-input",ee.forEach(i=>{const C=document.createElement("option");C.value=i.value,C.textContent=i.label,i.value===e.type&&(C.selected=!0),m.appendChild(C)}),m.addEventListener("change",()=>{u.textContent=ne(m.value);const i=a.querySelector(".fb-options-wrap");i&&(i.style.display=Q.has(m.value)?"":"none")}),T.appendChild(L),T.appendChild(m),c.appendChild(p),c.appendChild(f),c.appendChild(T);const N=document.createElement("div");N.style.cssText="display:grid;grid-template-columns:1fr 1fr auto;gap:.6rem;align-items:end;margin-bottom:.6rem;";const I=document.createElement("div"),P=document.createElement("label");P.className="form-label",P.textContent="Placeholder";const j=document.createElement("input");j.id=`fb-placeholder-${t}`,j.type="text",j.className="form-input",j.value=e.placeholder||"",I.appendChild(P),I.appendChild(j);const O=document.createElement("div"),V=document.createElement("label");V.className="form-label",V.textContent="Helper text";const q=document.createElement("input");q.id=`fb-helper-${t}`,q.type="text",q.className="form-input",q.value=e.helper||"",O.appendChild(V),O.appendChild(q);const Y=document.createElement("div");Y.style.cssText="padding-bottom:.35rem;";const M=document.createElement("label");M.style.cssText="display:flex;align-items:center;gap:.4rem;cursor:pointer;white-space:nowrap;";const z=document.createElement("input");z.id=`fb-required-${t}`,z.type="checkbox",z.checked=!!e.required,M.appendChild(z),M.appendChild(document.createTextNode("Required")),Y.appendChild(M),N.appendChild(I),N.appendChild(O),N.appendChild(Y);const A=document.createElement("div");A.className="fb-options-wrap",A.style.display=Q.has(e.type)?"":"none";const _=document.createElement("label");_.className="form-label",_.textContent="Options (one per line: value: Label)";const D=document.createElement("textarea");D.id=`fb-options-${t}`,D.className="form-input",D.rows=4,D.value=(e.options||[]).map(i=>typeof i=="string"?`${i}: ${i}`:`${i.value??""}: ${i.label??i.value??""}`).join(`
3
- `),A.appendChild(_),A.appendChild(D);const x=document.createElement("div");x.className="fb-grid-row",x.style.gridTemplateColumns="1fr auto",x.style.gap=".6rem",x.style.alignItems="end",x.style.marginBottom=".6rem",x.style.display=document.getElementById("collection-layout")?.value==="grid"?"grid":"none";const G=document.createElement("div"),J=document.createElement("label");J.className="form-label",J.textContent="Column Span";const k=document.createElement("input");k.id=`fb-span-${t}`,k.type="number",k.className="form-input",k.min="1",k.max="6",k.value=e.span>1?String(e.span):"1",G.appendChild(J),G.appendChild(k);const K=document.createElement("div");K.style.cssText="padding-bottom:.35rem;";const U=document.createElement("label");U.style.cssText="display:flex;align-items:center;gap:.4rem;cursor:pointer;white-space:nowrap;";const W=document.createElement("input");return W.id=`fb-fullwidth-${t}`,W.type="checkbox",W.checked=!!e.fullWidth,U.appendChild(W),U.appendChild(document.createTextNode("Full Width")),K.appendChild(U),x.appendChild(G),x.appendChild(K),a.appendChild(c),a.appendChild(N),a.appendChild(A),a.appendChild(x),d.addEventListener("click",()=>{const i=a.style.display!=="none";a.style.display=i?"none":"",l.style.transform=i?"":"rotate(180deg)"}),n.appendChild(d),n.appendChild(a),n}function H(e){if(e){if(e.textContent="",g.length===0){const t=document.createElement("p");t.className="text-muted",t.id="fields-empty-msg",t.style.cssText="text-align:center;padding:2rem 0;",t.textContent='No fields yet. Click "Add Field" to get started.',e.appendChild(t);return}g.forEach((t,n)=>{e.appendChild(de(t,n))})}}function ie(e,t){t.textContent="",te.forEach(n=>{const d=e?.[n]||{enabled:!1,access:"admin"},s=document.createElement("div");s.style.cssText="display:grid;grid-template-columns:140px 1fr 160px;gap:.75rem;align-items:center;padding:.6rem 0;border-bottom:1px solid var(--border-color,#333);";const r=document.createElement("strong");r.textContent=n.charAt(0).toUpperCase()+n.slice(1),r.style.cssText="font-size:.9rem;";const u=document.createElement("label");u.style.cssText="display:flex;align-items:center;gap:.45rem;cursor:pointer;font-size:.875rem;";const l=document.createElement("input");l.type="checkbox",l.id=`api-${n}-enabled`,l.checked=!!d.enabled,u.appendChild(l),u.appendChild(document.createTextNode("Enable public access"));const o=document.createElement("select");if(o.id=`api-${n}-access`,o.className="form-input",ae.forEach(a=>{const c=document.createElement("option");c.value=a,c.textContent=a.charAt(0).toUpperCase()+a.slice(1),a===d.access&&(c.selected=!0),o.appendChild(c)}),s.appendChild(r),s.appendChild(u),s.appendChild(o),t.appendChild(s),n==="read"){const a=document.createElement("div");a.style.cssText="display:grid;grid-template-columns:140px 1fr;gap:.75rem;align-items:center;padding:.6rem 0;border-bottom:1px solid var(--border-color,#333);";const c=document.createElement("label");c.htmlFor="api-read-fields",c.style.cssText="font-size:.85rem;color:var(--dm-text-muted,#aaa);",c.textContent="Read fields";const p=document.createElement("input");p.type="text",p.id="api-read-fields",p.className="form-input",p.placeholder="Comma-separated allowlist \u2014 empty = all fields",p.value=Array.isArray(d.fields)?d.fields.join(", "):"",a.appendChild(c),a.appendChild(p),t.appendChild(a)}})}function ce(e,t){E.dropdown("#storage-adapter-trigger",{items:[{label:"File (default)",value:"file"},{label:"MongoDB",value:"mongodb"}],onSelect:({item:d})=>{e.find("#storage-adapter").val(d.value),e.find("#storage-adapter-label").text(d.label);const s=d.value==="mongodb";e.find("#storage-connection-group").toggle(s),e.find("#storage-migration-warning").toggle(s&&!S)}});const n=t.map(d=>({label:d,value:d}));E.dropdown("#storage-connection-trigger",{items:n.length?n:[{label:"default",value:"default"}],onSelect:({item:d})=>{e.find("#storage-connection").val(d.value),e.find("#storage-connection-label").text(d.label)}})}function se(){return(document.getElementById("storage-adapter")?.value||"file")==="mongodb"?{adapter:"mongodb",connection:document.getElementById("storage-connection")?.value||"default"}:{adapter:"file"}}function re(){const e={};return te.forEach(t=>{const n=document.getElementById(`api-${t}-enabled`)?.checked??!1,d=document.getElementById(`api-${t}-access`)?.value||"admin";if(e[t]={enabled:n,access:d},t==="read"){const s=(document.getElementById("api-read-fields")?.value||"").split(",").map(r=>r.trim()).filter(Boolean);s.length&&(e[t].fields=s)}}),e}export const collectionEditorView={templateUrl:"/admin/js/templates/collection-editor.html",async onMount(e){g=[],h=null,S=!0;const t=window.location.hash.match(/\/collections\/edit\/([^/?#]+)/);t&&(h=t[1],S=!1),E.tabs(e.find("#collection-tabs").get(0)),e.find("#collection-layout").get(0)?.addEventListener("change",function(){const l=this.value==="grid";e.find("#collection-columns-group").get(0).style.display=l?"":"none",document.querySelectorAll(".fb-grid-row").forEach(o=>{o.style.display=l?"grid":"none"})});const n=e.find("#fields-list").get(0),d=e.find("#api-access-rows").get(0);X={};const s=e.find("#collection-project").get(0);if(s)try{(await w.projects.list()).forEach(o=>{const a=document.createElement("option");a.value=o.slug,a.textContent=o.name||o.slug,s.appendChild(a)})}catch{}const r=await w.collections.proStatus();r?.pro&&h!=="roles"&&(e.find("#storage-tab-btn").show(),ce(e,r.connections));let u={create:{enabled:!1,access:"admin"},read:{enabled:!0,access:"public"},update:{enabled:!1,access:"admin"},delete:{enabled:!1,access:"admin"}};if(S){const l=e.find("#field-title").get(0),o=e.find("#field-slug").get(0);l&&o&&(l.addEventListener("input",()=>{o.dataset.manual||(o.value=le(l.value))}),o.addEventListener("input",()=>{o.dataset.manual="1"}))}else try{const l=await w.collections.get(h);if(!l){E.toast("Collection not found.",{type:"error"}),R.navigate("/collections");return}const o=e.find("#editor-title-text").get(0);o&&(o.textContent=l.title),e.find("#field-title").val(l.title||""),e.find("#field-slug").val(l.slug||""),e.find("#field-slug").prop("readonly",!0),e.find("#slug-hint").get(0).textContent="Slug cannot be changed after creation.",e.find("#field-description").val(l.description||""),e.find("#collection-layout").val(l.layout||"stacked"),e.find("#collection-columns").val(l.columns||2),e.find("#collection-bundled").prop("checked",!!l.bundled),X=l.meta||{},e.find("#collection-project").val(l.meta?.project||""),e.find("#collection-columns-group").get(0).style.display=l.layout==="grid"?"":"none",g=l.fields||[],u=l.api||u,B=l.storage?.adapter||"file",l.storage&&(e.find("#storage-adapter").val(l.storage.adapter||"file"),e.find("#storage-adapter-label").text(l.storage.adapter==="mongodb"?"MongoDB":"File (default)"),l.storage.adapter==="mongodb"&&(e.find("#storage-connection-group").show(),e.find("#storage-connection").val(l.storage.connection||"default"),e.find("#storage-connection-label").text(l.storage.connection||"default"))),h==="roles"&&e.find("#storage-tab-btn").hide()}catch{E.toast("Failed to load collection.",{type:"error"}),R.navigate("/collections");return}H(n),ie(u,d),e.find("#add-field-btn").off("click").on("click",()=>{g=Z(),g.push({id:`field-${Date.now()}`,name:"",label:"",type:"string",required:!1,placeholder:"",helper:"",options:[],validation:[],logic:null}),H(n);const l=n.querySelectorAll(".fb-field-card");if(l.length){const o=l[l.length-1],a=o.querySelector(".fb-field-body"),c=o.querySelector(".fb-field-chevron");a&&(a.style.display=""),c&&(c.style.transform="rotate(180deg)"),o.querySelector(`#fb-label-${g.length-1}`)?.focus()}}),e.find("#save-collection-btn").off("click").on("click",async()=>{const l=e.find("#field-title").val().trim(),o=e.find("#field-slug").val().trim(),a=e.find("#field-description").val().trim();if(!l){E.toast("Title is required.",{type:"warning"});return}const c=Z(),p=re(),y=e.find("#collection-layout").val()||"stacked",b=parseInt(e.find("#collection-columns").val(),10)||2,f=se(),$=e.find("#collection-bundled").is(":checked"),v=e.find("#collection-project").val()||"",T={...X||{},project:v||null},L=e.find("#save-collection-btn");L.prop("disabled",!0);try{if(S){const m=await w.collections.create({title:l,slug:o,description:a,layout:y,columns:b,fields:c,api:p,storage:f,meta:T,...$?{bundled:!0}:{}});h=m.slug,B=f.adapter||"file",S=!1,E.toast("Collection created.",{type:"success"}),R.navigate(`/collections/edit/${m.slug}`)}else if((f.adapter||"file")!==B){let m=0;try{m=(await w.collections.listEntries(h,{limit:1}))?.total??0}catch{}const N=B==="file"?`file \u2192 ${f.adapter}`:`${B} \u2192 ${f.adapter||"file"}`;if(m>0&&await E.confirm(`You changed the storage adapter (${N}).
2
+ `).filter(c=>c.trim()).map(c=>{const[p,...y]=c.split(":");return{value:p.trim(),label:y.join(":").trim()||p.trim()}}))}const o=document.getElementById(`fb-span-${e}`);if(document.getElementById(`fb-fullwidth-${e}`)?.checked)t.fullWidth=!0,delete t.span;else{delete t.fullWidth;const a=parseInt(o?.value,10);a>1?t.span=a:delete t.span}return t}function Z(){return g.map((e,t)=>oe(t))}function de(e,t){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;margin-bottom:.75rem;overflow:hidden;";const d=document.createElement("div");d.className="fb-field-header",d.style.cssText="display:flex;align-items:center;gap:.5rem;padding:.6rem .75rem;background:var(--dm-card-header-bg,rgba(255,255,255,.03));cursor:pointer;user-select:none;";const s=document.createElement("span");s.textContent="\u283F",s.style.cssText="cursor:grab;opacity:.4;font-size:1.1rem;flex-shrink:0;",n.draggable=!0,s.addEventListener("mousedown",()=>{n.draggable=!0}),n.addEventListener("dragstart",i=>{F=t,i.dataTransfer.effectAllowed="move",n.style.opacity="0.4"}),n.addEventListener("dragend",()=>{n.style.opacity="",document.querySelectorAll(".fb-field-card").forEach(i=>i.classList.remove("fb-drag-over"))}),n.addEventListener("dragover",i=>{i.preventDefault(),i.dataTransfer.dropEffect="move",document.querySelectorAll(".fb-field-card").forEach(C=>C.classList.remove("fb-drag-over")),n.classList.add("fb-drag-over")}),n.addEventListener("dragleave",()=>{n.classList.remove("fb-drag-over")}),n.addEventListener("drop",i=>{if(i.preventDefault(),n.classList.remove("fb-drag-over"),F===null||F===t)return;g=Z();const[C]=g.splice(F,1);g.splice(t,0,C),F=null,H(document.getElementById("fields-list"))});const r=document.createElement("span");r.className="fb-field-summary",r.style.cssText="flex:1;font-weight:500;font-size:.9rem;",r.textContent=e.label||"(Untitled field)";const u=document.createElement("span");u.style.cssText="font-size:.75rem;opacity:.5;",u.textContent=ne(e.type);const l=document.createElement("span");l.className="fb-field-chevron",l.textContent="\u25BE",l.style.cssText="opacity:.5;transition:transform .2s;";const o=document.createElement("button");o.type="button",o.innerHTML='<span data-icon="x"></span>',o.className="btn btn-sm btn-ghost",o.style.cssText="padding:.15rem .45rem;line-height:1;opacity:.6;",o.title="Remove field",o.addEventListener("click",i=>{i.stopPropagation(),g.splice(t,1),H(document.getElementById("fields-list"))}),d.appendChild(s),d.appendChild(r),d.appendChild(u),d.appendChild(l),d.appendChild(o),Domma.icons.scan(o);const a=document.createElement("div");a.className="fb-field-body",a.style.cssText="padding:.75rem;display:none;";const c=document.createElement("div");c.style.cssText="display:grid;grid-template-columns:1fr 1fr 1fr;gap:.6rem;margin-bottom:.6rem;";const p=document.createElement("div"),y=document.createElement("label");y.className="form-label",y.textContent="Label";const b=document.createElement("input");b.id=`fb-label-${t}`,b.type="text",b.className="form-input",b.value=e.label||"",b.addEventListener("input",()=>{r.textContent=b.value.trim()||"(Untitled field)";const i=document.getElementById(`fb-name-${t}`);i&&!i.dataset.manual&&(i.value=le(b.value).replace(/-/g,"_"))}),p.appendChild(y),p.appendChild(b);const f=document.createElement("div"),$=document.createElement("label");$.className="form-label",$.textContent="Name (key)";const v=document.createElement("input");v.id=`fb-name-${t}`,v.type="text",v.className="form-input",v.value=e.name||"",v.addEventListener("input",()=>{v.dataset.manual="1"}),f.appendChild($),f.appendChild(v);const T=document.createElement("div"),L=document.createElement("label");L.className="form-label",L.textContent="Type";const m=document.createElement("select");m.id=`fb-type-${t}`,m.className="form-input",ee.forEach(i=>{const C=document.createElement("option");C.value=i.value,C.textContent=i.label,i.value===e.type&&(C.selected=!0),m.appendChild(C)}),m.addEventListener("change",()=>{u.textContent=ne(m.value);const i=a.querySelector(".fb-options-wrap");i&&(i.style.display=Q.has(m.value)?"":"none")}),T.appendChild(L),T.appendChild(m),c.appendChild(p),c.appendChild(f),c.appendChild(T);const N=document.createElement("div");N.style.cssText="display:grid;grid-template-columns:1fr 1fr auto;gap:.6rem;align-items:end;margin-bottom:.6rem;";const I=document.createElement("div"),P=document.createElement("label");P.className="form-label",P.textContent="Placeholder";const j=document.createElement("input");j.id=`fb-placeholder-${t}`,j.type="text",j.className="form-input",j.value=e.placeholder||"",I.appendChild(P),I.appendChild(j);const O=document.createElement("div"),V=document.createElement("label");V.className="form-label",V.textContent="Helper text";const q=document.createElement("input");q.id=`fb-helper-${t}`,q.type="text",q.className="form-input",q.value=e.helper||"",O.appendChild(V),O.appendChild(q);const Y=document.createElement("div");Y.style.cssText="padding-bottom:.35rem;";const M=document.createElement("label");M.style.cssText="display:flex;align-items:center;gap:.4rem;cursor:pointer;white-space:nowrap;";const z=document.createElement("input");z.id=`fb-required-${t}`,z.type="checkbox",z.checked=!!e.required,M.appendChild(z),M.appendChild(document.createTextNode("Required")),Y.appendChild(M),N.appendChild(I),N.appendChild(O),N.appendChild(Y);const A=document.createElement("div");A.className="fb-options-wrap",A.style.display=Q.has(e.type)?"":"none";const _=document.createElement("label");_.className="form-label",_.textContent="Options (one per line: value: Label)";const D=document.createElement("textarea");D.id=`fb-options-${t}`,D.className="form-input",D.rows=4,D.value=(e.options||[]).map(i=>typeof i=="string"?`${i}: ${i}`:`${i.value??""}: ${i.label??i.value??""}`).join(`
3
+ `),A.appendChild(_),A.appendChild(D);const x=document.createElement("div");x.className="fb-grid-row",x.style.gridTemplateColumns="1fr auto",x.style.gap=".6rem",x.style.alignItems="end",x.style.marginBottom=".6rem",x.style.display=document.getElementById("collection-layout")?.value==="grid"?"grid":"none";const G=document.createElement("div"),J=document.createElement("label");J.className="form-label",J.textContent="Column Span";const k=document.createElement("input");k.id=`fb-span-${t}`,k.type="number",k.className="form-input",k.min="1",k.max="6",k.value=e.span>1?String(e.span):"1",G.appendChild(J),G.appendChild(k);const K=document.createElement("div");K.style.cssText="padding-bottom:.35rem;";const U=document.createElement("label");U.style.cssText="display:flex;align-items:center;gap:.4rem;cursor:pointer;white-space:nowrap;";const W=document.createElement("input");return W.id=`fb-fullwidth-${t}`,W.type="checkbox",W.checked=!!e.fullWidth,U.appendChild(W),U.appendChild(document.createTextNode("Full Width")),K.appendChild(U),x.appendChild(G),x.appendChild(K),a.appendChild(c),a.appendChild(N),a.appendChild(A),a.appendChild(x),d.addEventListener("click",()=>{const i=a.style.display!=="none";a.style.display=i?"none":"",l.style.transform=i?"":"rotate(180deg)"}),n.appendChild(d),n.appendChild(a),n}function H(e){if(e){if(e.textContent="",g.length===0){const t=document.createElement("p");t.className="text-muted",t.id="fields-empty-msg",t.style.cssText="text-align:center;padding:2rem 0;",t.textContent='No fields yet. Click "Add Field" to get started.',e.appendChild(t);return}g.forEach((t,n)=>{e.appendChild(de(t,n))})}}function ie(e,t){t.textContent="",te.forEach(n=>{const d=e?.[n]||{enabled:!1,access:"admin"},s=document.createElement("div");s.style.cssText="display:grid;grid-template-columns:140px 1fr 160px;gap:.75rem;align-items:center;padding:.6rem 0;border-bottom:1px solid var(--dm-border,#333);";const r=document.createElement("strong");r.textContent=n.charAt(0).toUpperCase()+n.slice(1),r.style.cssText="font-size:.9rem;";const u=document.createElement("label");u.style.cssText="display:flex;align-items:center;gap:.45rem;cursor:pointer;font-size:.875rem;";const l=document.createElement("input");l.type="checkbox",l.id=`api-${n}-enabled`,l.checked=!!d.enabled,u.appendChild(l),u.appendChild(document.createTextNode("Enable public access"));const o=document.createElement("select");if(o.id=`api-${n}-access`,o.className="form-input",ae.forEach(a=>{const c=document.createElement("option");c.value=a,c.textContent=a.charAt(0).toUpperCase()+a.slice(1),a===d.access&&(c.selected=!0),o.appendChild(c)}),s.appendChild(r),s.appendChild(u),s.appendChild(o),t.appendChild(s),n==="read"){const a=document.createElement("div");a.style.cssText="display:grid;grid-template-columns:140px 1fr;gap:.75rem;align-items:center;padding:.6rem 0;border-bottom:1px solid var(--dm-border,#333);";const c=document.createElement("label");c.htmlFor="api-read-fields",c.style.cssText="font-size:.85rem;color:var(--dm-text-muted,#aaa);",c.textContent="Read fields";const p=document.createElement("input");p.type="text",p.id="api-read-fields",p.className="form-input",p.placeholder="Comma-separated allowlist \u2014 empty = all fields",p.value=Array.isArray(d.fields)?d.fields.join(", "):"",a.appendChild(c),a.appendChild(p),t.appendChild(a)}})}function ce(e,t){E.dropdown("#storage-adapter-trigger",{items:[{label:"File (default)",value:"file"},{label:"MongoDB",value:"mongodb"}],onSelect:({item:d})=>{e.find("#storage-adapter").val(d.value),e.find("#storage-adapter-label").text(d.label);const s=d.value==="mongodb";e.find("#storage-connection-group").toggle(s),e.find("#storage-migration-warning").toggle(s&&!S)}});const n=t.map(d=>({label:d,value:d}));E.dropdown("#storage-connection-trigger",{items:n.length?n:[{label:"default",value:"default"}],onSelect:({item:d})=>{e.find("#storage-connection").val(d.value),e.find("#storage-connection-label").text(d.label)}})}function se(){return(document.getElementById("storage-adapter")?.value||"file")==="mongodb"?{adapter:"mongodb",connection:document.getElementById("storage-connection")?.value||"default"}:{adapter:"file"}}function re(){const e={};return te.forEach(t=>{const n=document.getElementById(`api-${t}-enabled`)?.checked??!1,d=document.getElementById(`api-${t}-access`)?.value||"admin";if(e[t]={enabled:n,access:d},t==="read"){const s=(document.getElementById("api-read-fields")?.value||"").split(",").map(r=>r.trim()).filter(Boolean);s.length&&(e[t].fields=s)}}),e}export const collectionEditorView={templateUrl:"/admin/js/templates/collection-editor.html",async onMount(e){g=[],h=null,S=!0;const t=window.location.hash.match(/\/collections\/edit\/([^/?#]+)/);t&&(h=t[1],S=!1),E.tabs(e.find("#collection-tabs").get(0)),e.find("#collection-layout").get(0)?.addEventListener("change",function(){const l=this.value==="grid";e.find("#collection-columns-group").get(0).style.display=l?"":"none",document.querySelectorAll(".fb-grid-row").forEach(o=>{o.style.display=l?"grid":"none"})});const n=e.find("#fields-list").get(0),d=e.find("#api-access-rows").get(0);X={};const s=e.find("#collection-project").get(0);if(s)try{(await w.projects.list()).forEach(o=>{const a=document.createElement("option");a.value=o.slug,a.textContent=o.name||o.slug,s.appendChild(a)})}catch{}const r=await w.collections.proStatus();r?.pro&&h!=="roles"&&(e.find("#storage-tab-btn").show(),ce(e,r.connections));let u={create:{enabled:!1,access:"admin"},read:{enabled:!0,access:"public"},update:{enabled:!1,access:"admin"},delete:{enabled:!1,access:"admin"}};if(S){const l=e.find("#field-title").get(0),o=e.find("#field-slug").get(0);l&&o&&(l.addEventListener("input",()=>{o.dataset.manual||(o.value=le(l.value))}),o.addEventListener("input",()=>{o.dataset.manual="1"}))}else try{const l=await w.collections.get(h);if(!l){E.toast("Collection not found.",{type:"error"}),R.navigate("/collections");return}const o=e.find("#editor-title-text").get(0);o&&(o.textContent=l.title),e.find("#field-title").val(l.title||""),e.find("#field-slug").val(l.slug||""),e.find("#field-slug").prop("readonly",!0),e.find("#slug-hint").get(0).textContent="Slug cannot be changed after creation.",e.find("#field-description").val(l.description||""),e.find("#collection-layout").val(l.layout||"stacked"),e.find("#collection-columns").val(l.columns||2),e.find("#collection-bundled").prop("checked",!!l.bundled),X=l.meta||{},e.find("#collection-project").val(l.meta?.project||""),e.find("#collection-columns-group").get(0).style.display=l.layout==="grid"?"":"none",g=l.fields||[],u=l.api||u,B=l.storage?.adapter||"file",l.storage&&(e.find("#storage-adapter").val(l.storage.adapter||"file"),e.find("#storage-adapter-label").text(l.storage.adapter==="mongodb"?"MongoDB":"File (default)"),l.storage.adapter==="mongodb"&&(e.find("#storage-connection-group").show(),e.find("#storage-connection").val(l.storage.connection||"default"),e.find("#storage-connection-label").text(l.storage.connection||"default"))),h==="roles"&&e.find("#storage-tab-btn").hide()}catch{E.toast("Failed to load collection.",{type:"error"}),R.navigate("/collections");return}H(n),ie(u,d),e.find("#add-field-btn").off("click").on("click",()=>{g=Z(),g.push({id:`field-${Date.now()}`,name:"",label:"",type:"string",required:!1,placeholder:"",helper:"",options:[],validation:[],logic:null}),H(n);const l=n.querySelectorAll(".fb-field-card");if(l.length){const o=l[l.length-1],a=o.querySelector(".fb-field-body"),c=o.querySelector(".fb-field-chevron");a&&(a.style.display=""),c&&(c.style.transform="rotate(180deg)"),o.querySelector(`#fb-label-${g.length-1}`)?.focus()}}),e.find("#save-collection-btn").off("click").on("click",async()=>{const l=e.find("#field-title").val().trim(),o=e.find("#field-slug").val().trim(),a=e.find("#field-description").val().trim();if(!l){E.toast("Title is required.",{type:"warning"});return}const c=Z(),p=re(),y=e.find("#collection-layout").val()||"stacked",b=parseInt(e.find("#collection-columns").val(),10)||2,f=se(),$=e.find("#collection-bundled").is(":checked"),v=e.find("#collection-project").val()||"",T={...X||{},project:v||null},L=e.find("#save-collection-btn");L.prop("disabled",!0);try{if(S){const m=await w.collections.create({title:l,slug:o,description:a,layout:y,columns:b,fields:c,api:p,storage:f,meta:T,...$?{bundled:!0}:{}});h=m.slug,B=f.adapter||"file",S=!1,E.toast("Collection created.",{type:"success"}),R.navigate(`/collections/edit/${m.slug}`)}else if((f.adapter||"file")!==B){let m=0;try{m=(await w.collections.listEntries(h,{limit:1}))?.total??0}catch{}const N=B==="file"?`file \u2192 ${f.adapter}`:`${B} \u2192 ${f.adapter||"file"}`;if(m>0&&await E.confirm(`You changed the storage adapter (${N}).
4
4
 
5
5
  Migrate ${m} existing ${m===1?"entry":"entries"} to the new storage?`)){const I=await w.collections.migrateStorage(h,f);B=f.adapter||"file",E.toast(`Migrated ${I.migrated} of ${I.total} entries.`,{type:"success"})}else await w.collections.update(h,{title:l,description:a,layout:y,columns:b,fields:c,api:p,storage:f,meta:T,...$?{bundled:!0}:{bundled:!1}}),B=f.adapter||"file",E.toast("Collection saved.",{type:"success"})}else await w.collections.update(h,{title:l,description:a,layout:y,columns:b,fields:c,api:p,storage:f,meta:T,...$?{bundled:!0}:{bundled:!1}}),E.toast("Collection saved.",{type:"success"})}catch(m){E.toast(m.message||"Failed to save.",{type:"error"})}finally{L.prop("disabled",!1)}}),Domma.icons.scan()}};
@@ -1 +1 @@
1
- import{api as h}from"../api.js";function M(t){return String(t).replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")}let f=null,g=null,p=[],j=1,w=[],x={};const z=50;export const collectionEntriesView={templateUrl:"/admin/js/templates/collection-entries.html",async onMount(t){p=[],j=1,g=null,w=[],x={};const d=window.location.hash.match(/\/collections\/([^/?#]+)\/entries/);if(f=d?d[1]:null,!f){E.toast("No collection selected.",{type:"error"});return}try{if(g=await h.collections.get(f),!g){E.toast("Collection not found.",{type:"error"}),R.navigate("/collections");return}const o=t.find("#entries-title").get(0);o&&(o.textContent=g.title+" \u2014 Entries");const a=t.find("#edit-schema-btn").get(0);a&&a.setAttribute("href",`#/collections/edit/${f}`);const e=t.find("#form-btn").get(0);e&&(new Set(["roles","user-profiles"]).has(f)?e.style.display="none":e.setAttribute("href",`#/forms/edit/${f}`))}catch{E.toast("Failed to load collection schema.",{type:"error"});return}try{w=await h.actions.forCollection(f)}catch{w=[]}await v(t),await k(t);const n=t.find("#entry-search").get(0);n&&n.addEventListener("input",()=>{const o=n.value.toLowerCase().trim(),a=o?p.filter(e=>Object.values(e.data||{}).some(l=>String(l).toLowerCase().includes(o))):p;C(a,t)}),t.find("#add-entry-btn").off("click").on("click",()=>{A(null,t)}),t.find("#export-btn").off("click").on("click",()=>{const o=E.slideover({title:"Export Entries",size:"sm",position:"right"}),a=document.createElement("div");a.style.cssText="padding:1.25rem;display:flex;flex-direction:column;gap:1rem;",["json","csv"].forEach(e=>{const l=document.createElement("button");l.className="btn btn-ghost",l.style.cssText="justify-content:flex-start;gap:.5rem;",l.textContent=e==="json"?"Export as JSON":"Export as CSV",l.addEventListener("click",()=>{fetch(`/api/collections/${f}/export?format=${e}`,{headers:{Authorization:`Bearer ${S.get("auth_token")||""}`}}).then(s=>s.blob()).then(s=>{const r=document.createElement("a");r.href=URL.createObjectURL(s),r.download=`${f}-entries.${e}`,document.body.appendChild(r),r.click(),document.body.removeChild(r),URL.revokeObjectURL(r.href),o.close()}).catch(()=>E.toast("Export failed.",{type:"error"}))}),a.appendChild(l)}),o.setContent(a),o.open()}),t.find("#import-btn").off("click").on("click",()=>{B(t)}),t.find("#clear-all-btn").off("click").on("click",async()=>{if(await E.confirm("Delete ALL entries? This cannot be undone."))try{await h.collections.clearEntries(f),p=[],E.toast("All entries cleared.",{type:"success"}),C([],t)}catch{E.toast("Failed to clear entries.",{type:"error"})}}),Domma.icons.scan()}};async function k(t){const m=w.filter(n=>n.access?.rowLevel);if(m.length===0)return;const d=p.map(n=>n.id);d.length!==0&&(await Promise.all(m.map(async n=>{try{const{allowed:o}=await h.actions.checkAccess(n.slug,d);x[n.slug]=new Set(o)}catch{x[n.slug]=null}})),C(p,t))}async function v(t){try{const m=await h.collections.listEntries(f,{limit:500});p=m.entries||m||[]}catch{p=[],E.toast("Could not load entries.",{type:"error"})}C(p,t),await k(t)}function C(t,m){const d=m.find("#entry-count").get(0);d&&(d.textContent=t.length===p.length?`${p.length} entr${p.length!==1?"ies":"y"}`:`Showing ${t.length} of ${p.length}`);const o=[...(g?.fields||[]).slice(0,5).map(e=>({key:`data.${e.name}`,title:e.label||e.name,render:(l,s)=>{const r=s.data?.[e.name]??"",c=String(r),i=document.createElement("span");return i.title=c,i.textContent=c.length>60?c.slice(0,60)+"\u2026":c,i.outerHTML}})),{key:"meta",title:"Created",render:e=>D(e?.createdAt).format("DD MMM YYYY HH:mm")},{key:"id",title:"",render:e=>{const l=document.createElement("div");l.style.cssText="display:flex;gap:.3rem;justify-content:flex-end;flex-wrap:wrap;",w.forEach(c=>{if(x[c.slug]!==void 0&&x[c.slug]!==null&&!x[c.slug].has(e))return;const i=document.createElement("button");i.className="btn btn-sm btn-ghost js-run-action",i.dataset.actionSlug=c.slug,i.dataset.entryId=e,i.dataset.confirmMessage=c.trigger?.confirmMessage||"",i.title=c.title||c.slug;const y=document.createElement("span");y.setAttribute("data-icon",M(c.trigger?.icon||"zap")),i.appendChild(y),i.appendChild(document.createTextNode(" "+(c.trigger?.label||c.title))),l.appendChild(i)});const s=document.createElement("button");s.className="btn btn-sm btn-ghost js-edit-entry",s.dataset.id=e,s.setAttribute("data-tooltip","Edit"),s.innerHTML='<span data-icon="edit"></span>';const r=document.createElement("button");return r.className="btn btn-sm btn-danger js-delete-entry",r.dataset.id=e,r.setAttribute("data-tooltip","Delete"),r.innerHTML='<span data-icon="trash"></span>',l.appendChild(s),l.appendChild(r),l.outerHTML}}];T.create("#entries-table",{data:t,columns:o,emptyMessage:'No entries yet. Click "Add Entry" to get started.'});const a=document.querySelector("#entries-table");a&&(a.querySelectorAll(".js-run-action").forEach(e=>{e.addEventListener("click",async l=>{l.stopPropagation();const{actionSlug:s,entryId:r,confirmMessage:c}=e.dataset;if(!(c&&!await E.confirm(c))){e.disabled=!0;try{const i=await h.actions.execute(s,r);if(i.success)E.toast(`Action completed (${i.stepsCompleted} step${i.stepsCompleted!==1?"s":""}).`,{type:"success"}),await v({find:y=>({get:u=>document.querySelector(y)})});else{const y=i.results?.find(u=>!u.success);E.toast(y?.error||"Action failed.",{type:"error"})}}catch(i){E.toast(i.message||"Action failed.",{type:"error"})}finally{e.disabled=!1}}})}),a.querySelectorAll(".js-edit-entry").forEach(e=>{e.addEventListener("click",l=>{l.stopPropagation();const s=p.find(r=>r.id===e.dataset.id);s&&A(s,null)})}),a.querySelectorAll(".js-delete-entry").forEach(e=>{e.addEventListener("click",async l=>{if(l.stopPropagation(),!!await E.confirm("Delete this entry?"))try{await h.collections.deleteEntry(f,e.dataset.id),p=p.filter(r=>r.id!==e.dataset.id),E.toast("Entry deleted.",{type:"success"}),C(p,{find:r=>({get:c=>document.querySelector(r)})})}catch{E.toast("Failed to delete entry.",{type:"error"})}})}),a.querySelectorAll("tbody tr").forEach((e,l)=>{const s=t[l];s&&(e.style.cursor="pointer",e.addEventListener("click",r=>{r.target.closest(".js-edit-entry, .js-delete-entry")||q(s)}))}),Domma.icons.scan(a),a.querySelectorAll("[data-tooltip]").forEach(e=>{E.tooltip(e,{content:e.getAttribute("data-tooltip"),position:"top"})}))}function N(t,m){const d={};return t.forEach(n=>{const o={};n.fullWidth?o.span=m:n.span>1&&(o.span=n.span),n.placeholder&&(o.placeholder=n.placeholder),n.helper&&(o.hint=n.helper);const a=n.type==="checkbox"?"boolean":n.type==="date"?"string":n.type;d[n.name]={type:a,label:n.label,required:n.required,options:n.options,...Object.keys(o).length?{formConfig:o}:{}}}),d}function A(t,m){const d=!!t,n=E.modal({title:d?"Edit Entry":"Add Entry",size:"md"}),o=document.createElement("div");o.style.cssText="padding:.25rem 0 .5rem;";const a=g?.fields||[],e=g?.columns||2,l=g?.layout||"stacked",s=document.createElement("div");F.create(N(a,e),t?.data||{},{layout:l,columns:e,showSubmitButton:!1}).renderTo(s),(a||[]).forEach(y=>{if(y.type!=="date"||!y.name)return;const u=s.querySelector(`[name="${y.name}"]`);u&&u.type!=="date"&&(u.type="date")}),o.appendChild(s);const r=document.createElement("div");r.style.cssText="display:flex;justify-content:flex-end;gap:.5rem;margin-top:.5rem;";const c=document.createElement("button");c.className="btn btn-ghost",c.textContent="Cancel";const i=document.createElement("button");i.className="btn btn-primary",i.textContent=d?"Save Changes":"Add Entry",r.appendChild(c),r.appendChild(i),o.appendChild(r),n.element.appendChild(o),n.open(),c.addEventListener("click",()=>n.close()),i.addEventListener("click",async()=>{const y={};a.forEach(u=>{const b=s.querySelector(`[name="${u.name}"]`);b&&(y[u.name]=u.type==="checkbox"?b.checked:b.value)}),i.disabled=!0;try{if(d){const u=await h.collections.updateEntry(f,t.id,y),b=p.findIndex(L=>L.id===t.id);b!==-1&&(p[b]=u),E.toast("Entry updated.",{type:"success"})}else{const u=await h.collections.createEntry(f,y);p.unshift(u),E.toast("Entry added.",{type:"success"})}n.close(),C(p,m||{find:()=>({get:()=>null})})}catch(u){E.toast(u.message||"Failed to save entry.",{type:"error"}),i.disabled=!1}})}function q(t){const m=document.createElement("div"),d=g?.fields||[],n=document.createElement("div");n.style.cssText="display:flex;flex-direction:column;gap:.75rem;margin-bottom:1.25rem;",d.forEach(e=>{const l=t.data?.[e.name];if(l==null||l==="")return;const s=document.createElement("div");s.style.cssText="border-bottom:1px solid var(--border-color,#333);padding-bottom:.6rem;";const r=document.createElement("strong");r.textContent=e.label||e.name,r.style.cssText="display:block;font-size:.8rem;color:var(--text-muted,#888);margin-bottom:.2rem;";const c=document.createElement("p");c.textContent=String(l),c.style.cssText="margin:0;word-break:break-word;",s.appendChild(r),s.appendChild(c),n.appendChild(s)});const o=document.createElement("div");if(o.style.cssText="font-size:.8rem;color:var(--text-muted,#888);display:flex;flex-direction:column;gap:.3rem;border-top:1px solid var(--border-color,#333);padding-top:.75rem;",t.meta?.createdAt){const e=document.createElement("span");e.textContent=`Created: ${D(t.meta.createdAt).format("DD MMM YYYY HH:mm")}`,o.appendChild(e)}if(t.meta?.source){const e=document.createElement("span");e.textContent=`Source: ${t.meta.source}`,o.appendChild(e)}m.appendChild(n),m.appendChild(o);const a=E.modal({title:"Entry Details",size:"md"});a.element.appendChild(m),a.open()}function B(t){const m=E.modal({title:"Import Entries",size:"md"}),d=document.createElement("div");d.style.cssText="padding:.25rem 0 .5rem;display:flex;flex-direction:column;gap:.75rem;";const n=document.createElement("p");n.textContent='Paste a JSON array of entries. Each item should have a "data" object with field values.',n.style.cssText="font-size:.875rem;color:var(--text-muted,#888);margin:0;",d.appendChild(n);const o=document.createElement("textarea");o.className="form-input",o.rows=10,o.placeholder='[{"data": {"name": "Example"}}, ...]',d.appendChild(o);const a=document.createElement("p");a.style.cssText="font-size:.875rem;margin:0;display:none;",d.appendChild(a);const e=document.createElement("div");e.style.cssText="display:flex;justify-content:flex-end;gap:.5rem;";const l=document.createElement("button");l.className="btn btn-ghost",l.textContent="Cancel";const s=document.createElement("button");s.className="btn btn-primary",s.textContent="Import",e.appendChild(l),e.appendChild(s),d.appendChild(e),m.element.appendChild(d),m.open(),l.addEventListener("click",()=>m.close()),s.addEventListener("click",async()=>{let r;try{if(r=JSON.parse(o.value),!Array.isArray(r))throw new Error("Must be a JSON array")}catch(c){a.style.display="",a.style.color="var(--danger,#f87171)",a.textContent=`Invalid JSON: ${c.message}`;return}s.disabled=!0;try{const c=await h.collections.import(f,r);a.style.display="",a.style.color="var(--success,#4ade80)",a.textContent=`Imported ${c.imported} entries. Skipped: ${c.skipped}.`,await v(t||{find:()=>({get:()=>null,off:()=>({on:()=>{}})})}),setTimeout(()=>m.close(),1500)}catch(c){a.style.display="",a.style.color="var(--danger,#f87171)",a.textContent=c.message||"Import failed."}finally{s.disabled=!1}})}
1
+ import{api as h}from"../api.js";function M(t){return String(t).replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")}let f=null,g=null,p=[],j=1,w=[],x={};const z=50;export const collectionEntriesView={templateUrl:"/admin/js/templates/collection-entries.html",async onMount(t){p=[],j=1,g=null,w=[],x={};const d=window.location.hash.match(/\/collections\/([^/?#]+)\/entries/);if(f=d?d[1]:null,!f){E.toast("No collection selected.",{type:"error"});return}try{if(g=await h.collections.get(f),!g){E.toast("Collection not found.",{type:"error"}),R.navigate("/collections");return}const a=t.find("#entries-title").get(0);a&&(a.textContent=g.title+" \u2014 Entries");const o=t.find("#edit-schema-btn").get(0);o&&o.setAttribute("href",`#/collections/edit/${f}`);const e=t.find("#form-btn").get(0);e&&(new Set(["roles","user-profiles"]).has(f)?e.style.display="none":e.setAttribute("href",`#/forms/edit/${f}`))}catch{E.toast("Failed to load collection schema.",{type:"error"});return}try{w=await h.actions.forCollection(f)}catch{w=[]}await v(t),await k(t);const n=t.find("#entry-search").get(0);n&&n.addEventListener("input",()=>{const a=n.value.toLowerCase().trim(),o=a?p.filter(e=>Object.values(e.data||{}).some(l=>String(l).toLowerCase().includes(a))):p;C(o,t)}),t.find("#add-entry-btn").off("click").on("click",()=>{A(null,t)}),t.find("#export-btn").off("click").on("click",()=>{const a=E.slideover({title:"Export Entries",size:"sm",position:"right"}),o=document.createElement("div");o.style.cssText="padding:1.25rem;display:flex;flex-direction:column;gap:1rem;",["json","csv"].forEach(e=>{const l=document.createElement("button");l.className="btn btn-ghost",l.style.cssText="justify-content:flex-start;gap:.5rem;",l.textContent=e==="json"?"Export as JSON":"Export as CSV",l.addEventListener("click",()=>{fetch(`/api/collections/${f}/export?format=${e}`,{headers:{Authorization:`Bearer ${S.get("auth_token")||""}`}}).then(s=>s.blob()).then(s=>{const r=document.createElement("a");r.href=URL.createObjectURL(s),r.download=`${f}-entries.${e}`,document.body.appendChild(r),r.click(),document.body.removeChild(r),URL.revokeObjectURL(r.href),a.close()}).catch(()=>E.toast("Export failed.",{type:"error"}))}),o.appendChild(l)}),a.setContent(o),a.open()}),t.find("#import-btn").off("click").on("click",()=>{B(t)}),t.find("#clear-all-btn").off("click").on("click",async()=>{if(await E.confirm("Delete ALL entries? This cannot be undone."))try{await h.collections.clearEntries(f),p=[],E.toast("All entries cleared.",{type:"success"}),C([],t)}catch{E.toast("Failed to clear entries.",{type:"error"})}}),Domma.icons.scan()}};async function k(t){const m=w.filter(n=>n.access?.rowLevel);if(m.length===0)return;const d=p.map(n=>n.id);d.length!==0&&(await Promise.all(m.map(async n=>{try{const{allowed:a}=await h.actions.checkAccess(n.slug,d);x[n.slug]=new Set(a)}catch{x[n.slug]=null}})),C(p,t))}async function v(t){try{const m=await h.collections.listEntries(f,{limit:500});p=m.entries||m||[]}catch{p=[],E.toast("Could not load entries.",{type:"error"})}C(p,t),await k(t)}function C(t,m){const d=m.find("#entry-count").get(0);d&&(d.textContent=t.length===p.length?`${p.length} entr${p.length!==1?"ies":"y"}`:`Showing ${t.length} of ${p.length}`);const a=[...(g?.fields||[]).slice(0,5).map(e=>({key:`data.${e.name}`,title:e.label||e.name,render:(l,s)=>{const r=s.data?.[e.name]??"",c=String(r),i=document.createElement("span");return i.title=c,i.textContent=c.length>60?c.slice(0,60)+"\u2026":c,i.outerHTML}})),{key:"meta",title:"Created",render:e=>D(e?.createdAt).format("DD MMM YYYY HH:mm")},{key:"id",title:"",render:e=>{const l=document.createElement("div");l.style.cssText="display:flex;gap:.3rem;justify-content:flex-end;flex-wrap:wrap;",w.forEach(c=>{if(x[c.slug]!==void 0&&x[c.slug]!==null&&!x[c.slug].has(e))return;const i=document.createElement("button");i.className="btn btn-sm btn-ghost js-run-action",i.dataset.actionSlug=c.slug,i.dataset.entryId=e,i.dataset.confirmMessage=c.trigger?.confirmMessage||"",i.title=c.title||c.slug;const y=document.createElement("span");y.setAttribute("data-icon",M(c.trigger?.icon||"zap")),i.appendChild(y),i.appendChild(document.createTextNode(" "+(c.trigger?.label||c.title))),l.appendChild(i)});const s=document.createElement("button");s.className="btn btn-sm btn-ghost js-edit-entry",s.dataset.id=e,s.setAttribute("data-tooltip","Edit"),s.innerHTML='<span data-icon="edit"></span>';const r=document.createElement("button");return r.className="btn btn-sm btn-danger js-delete-entry",r.dataset.id=e,r.setAttribute("data-tooltip","Delete"),r.innerHTML='<span data-icon="trash"></span>',l.appendChild(s),l.appendChild(r),l.outerHTML}}];T.create("#entries-table",{data:t,columns:a,emptyMessage:'No entries yet. Click "Add Entry" to get started.'});const o=document.querySelector("#entries-table");o&&(o.querySelectorAll(".js-run-action").forEach(e=>{e.addEventListener("click",async l=>{l.stopPropagation();const{actionSlug:s,entryId:r,confirmMessage:c}=e.dataset;if(!(c&&!await E.confirm(c))){e.disabled=!0;try{const i=await h.actions.execute(s,r);if(i.success)E.toast(`Action completed (${i.stepsCompleted} step${i.stepsCompleted!==1?"s":""}).`,{type:"success"}),await v({find:y=>({get:u=>document.querySelector(y)})});else{const y=i.results?.find(u=>!u.success);E.toast(y?.error||"Action failed.",{type:"error"})}}catch(i){E.toast(i.message||"Action failed.",{type:"error"})}finally{e.disabled=!1}}})}),o.querySelectorAll(".js-edit-entry").forEach(e=>{e.addEventListener("click",l=>{l.stopPropagation();const s=p.find(r=>r.id===e.dataset.id);s&&A(s,null)})}),o.querySelectorAll(".js-delete-entry").forEach(e=>{e.addEventListener("click",async l=>{if(l.stopPropagation(),!!await E.confirm("Delete this entry?"))try{await h.collections.deleteEntry(f,e.dataset.id),p=p.filter(r=>r.id!==e.dataset.id),E.toast("Entry deleted.",{type:"success"}),C(p,{find:r=>({get:c=>document.querySelector(r)})})}catch{E.toast("Failed to delete entry.",{type:"error"})}})}),o.querySelectorAll("tbody tr").forEach((e,l)=>{const s=t[l];s&&(e.style.cursor="pointer",e.addEventListener("click",r=>{r.target.closest(".js-edit-entry, .js-delete-entry")||q(s)}))}),Domma.icons.scan(o),o.querySelectorAll("[data-tooltip]").forEach(e=>{E.tooltip(e,{content:e.getAttribute("data-tooltip"),position:"top"})}))}function N(t,m){const d={};return t.forEach(n=>{const a={};n.fullWidth?a.span=m:n.span>1&&(a.span=n.span),n.placeholder&&(a.placeholder=n.placeholder),n.helper&&(a.hint=n.helper);const o=n.type==="checkbox"?"boolean":n.type==="date"?"string":n.type;d[n.name]={type:o,label:n.label,required:n.required,options:n.options,...Object.keys(a).length?{formConfig:a}:{}}}),d}function A(t,m){const d=!!t,n=E.modal({title:d?"Edit Entry":"Add Entry",size:"md"}),a=document.createElement("div");a.style.cssText="padding:.25rem 0 .5rem;";const o=g?.fields||[],e=g?.columns||2,l=g?.layout||"stacked",s=document.createElement("div");F.create(N(o,e),t?.data||{},{layout:l,columns:e,showSubmitButton:!1}).renderTo(s),(o||[]).forEach(y=>{if(y.type!=="date"||!y.name)return;const u=s.querySelector(`[name="${y.name}"]`);u&&u.type!=="date"&&(u.type="date")}),a.appendChild(s);const r=document.createElement("div");r.style.cssText="display:flex;justify-content:flex-end;gap:.5rem;margin-top:.5rem;";const c=document.createElement("button");c.className="btn btn-ghost",c.textContent="Cancel";const i=document.createElement("button");i.className="btn btn-primary",i.textContent=d?"Save Changes":"Add Entry",r.appendChild(c),r.appendChild(i),a.appendChild(r),n.element.appendChild(a),n.open(),c.addEventListener("click",()=>n.close()),i.addEventListener("click",async()=>{const y={};o.forEach(u=>{const b=s.querySelector(`[name="${u.name}"]`);b&&(y[u.name]=u.type==="checkbox"?b.checked:b.value)}),i.disabled=!0;try{if(d){const u=await h.collections.updateEntry(f,t.id,y),b=p.findIndex(L=>L.id===t.id);b!==-1&&(p[b]=u),E.toast("Entry updated.",{type:"success"})}else{const u=await h.collections.createEntry(f,y);p.unshift(u),E.toast("Entry added.",{type:"success"})}n.close(),C(p,m||{find:()=>({get:()=>null})})}catch(u){E.toast(u.message||"Failed to save entry.",{type:"error"}),i.disabled=!1}})}function q(t){const m=document.createElement("div"),d=g?.fields||[],n=document.createElement("div");n.style.cssText="display:flex;flex-direction:column;gap:.75rem;margin-bottom:1.25rem;",d.forEach(e=>{const l=t.data?.[e.name];if(l==null||l==="")return;const s=document.createElement("div");s.style.cssText="border-bottom:1px solid var(--dm-border,#333);padding-bottom:.6rem;";const r=document.createElement("strong");r.textContent=e.label||e.name,r.style.cssText="display:block;font-size:.8rem;color:var(--dm-text-muted,#888);margin-bottom:.2rem;";const c=document.createElement("p");c.textContent=String(l),c.style.cssText="margin:0;word-break:break-word;",s.appendChild(r),s.appendChild(c),n.appendChild(s)});const a=document.createElement("div");if(a.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;",t.meta?.createdAt){const e=document.createElement("span");e.textContent=`Created: ${D(t.meta.createdAt).format("DD MMM YYYY HH:mm")}`,a.appendChild(e)}if(t.meta?.source){const e=document.createElement("span");e.textContent=`Source: ${t.meta.source}`,a.appendChild(e)}m.appendChild(n),m.appendChild(a);const o=E.modal({title:"Entry Details",size:"md"});o.element.appendChild(m),o.open()}function B(t){const m=E.modal({title:"Import Entries",size:"md"}),d=document.createElement("div");d.style.cssText="padding:.25rem 0 .5rem;display:flex;flex-direction:column;gap:.75rem;";const n=document.createElement("p");n.textContent='Paste a JSON array of entries. Each item should have a "data" object with field values.',n.style.cssText="font-size:.875rem;color:var(--dm-text-muted,#888);margin:0;",d.appendChild(n);const a=document.createElement("textarea");a.className="form-input",a.rows=10,a.placeholder='[{"data": {"name": "Example"}}, ...]',d.appendChild(a);const o=document.createElement("p");o.style.cssText="font-size:.875rem;margin:0;display:none;",d.appendChild(o);const e=document.createElement("div");e.style.cssText="display:flex;justify-content:flex-end;gap:.5rem;";const l=document.createElement("button");l.className="btn btn-ghost",l.textContent="Cancel";const s=document.createElement("button");s.className="btn btn-primary",s.textContent="Import",e.appendChild(l),e.appendChild(s),d.appendChild(e),m.element.appendChild(d),m.open(),l.addEventListener("click",()=>m.close()),s.addEventListener("click",async()=>{let r;try{if(r=JSON.parse(a.value),!Array.isArray(r))throw new Error("Must be a JSON array")}catch(c){o.style.display="",o.style.color="var(--dm-danger,#f87171)",o.textContent=`Invalid JSON: ${c.message}`;return}s.disabled=!0;try{const c=await h.collections.import(f,r);o.style.display="",o.style.color="var(--dm-success,#4ade80)",o.textContent=`Imported ${c.imported} entries. Skipped: ${c.skipped}.`,await v(t||{find:()=>({get:()=>null,off:()=>({on:()=>{}})})}),setTimeout(()=>m.close(),1500)}catch(c){o.style.display="",o.style.color="var(--dm-danger,#f87171)",o.textContent=c.message||"Import failed."}finally{s.disabled=!1}})}
@@ -1,4 +1,4 @@
1
- import{api as u}from"../api.js";import{createSimpleEditor as S}from"../lib/simple-editor.js";let p=null,b=null,f=null,l={},h={};const c={};export const componentEditorView={templateUrl:"/admin/js/templates/component-editor.html",async onMount(e){p=null,f=null,l={},h={};for(const t of Object.keys(c))c[t]?.destroy?.(),delete c[t];const n=window.location.hash.match(/\/components\/edit\/([^/?#]+)/);n&&(p=decodeURIComponent(n[1])),A(e),await T(e),L(e),z(e),await D(e);const o=e.find("#component-preview-iframe").get(0);o.contentDocument?.readyState==="complete"?v(e):o.addEventListener("load",()=>v(e),{once:!0}),Domma.icons.scan(e.get(0))}};async function T(e){const n=e.find("#component-name").get(0),o=e.find("#component-tag-hint").get(0),t=e.find("#component-editor-title").get(0),s=e.find("#export-component-btn").get(0),a=()=>{const i=(n.value||"").trim()||"\u2026";o.textContent=`<dm-${i}>`},r=e.find("#component-project").get(0);if(r)try{const i=await u.projects.list();for(const m of i){const d=document.createElement("option");d.value=m.slug,d.textContent=m.name||m.slug,r.appendChild(d)}}catch{}if(p){t.textContent="Edit Component",n.value=p,n.disabled=!0,s&&(s.style.display="");try{const i=await u.components.get(p);w(e,i.source),e.find("#component-bundled").prop("checked",!!i.bundled),h=i.meta||{},r&&(r.value=i.meta?.project||"")}catch(i){E.toast(i.message||"Component not found.",{type:"error"}),R.navigate("/components");return}}else t.textContent="New Component",w(e,V);a(),n.addEventListener("input",a)}const V=`<template>
1
+ import{api as u}from"../api.js";import{createSimpleEditor as S}from"../lib/simple-editor.js";let p=null,b=null,f=null,l={},v={};const c={};export const componentEditorView={templateUrl:"/admin/js/templates/component-editor.html",async onMount(e){p=null,f=null,l={},v={};for(const t of Object.keys(c))c[t]?.destroy?.(),delete c[t];const n=window.location.hash.match(/\/components\/edit\/([^/?#]+)/);n&&(p=decodeURIComponent(n[1])),A(e),await T(e),L(e),z(e),await D(e);const o=e.find("#component-preview-iframe").get(0);o.contentDocument?.readyState==="complete"?h(e):o.addEventListener("load",()=>h(e),{once:!0}),Domma.icons.scan(e.get(0))}};async function T(e){const n=e.find("#component-name").get(0),o=e.find("#component-tag-hint").get(0),t=e.find("#component-editor-title").get(0),s=e.find("#export-component-btn").get(0),r=()=>{const i=(n.value||"").trim()||"\u2026";o.textContent=`<dm-${i}>`},a=e.find("#component-project").get(0);if(a)try{const i=await u.projects.list();for(const m of i){const d=document.createElement("option");d.value=m.slug,d.textContent=m.name||m.slug,a.appendChild(d)}}catch{}if(p){t.textContent="Edit Component",n.value=p,n.disabled=!0,s&&(s.style.display="");try{const i=await u.components.get(p);w(e,i.source),e.find("#component-bundled").prop("checked",!!i.bundled),v=i.meta||{},a&&(a.value=i.meta?.project||"")}catch(i){E.toast(i.message||"Component not found.",{type:"error"}),R.navigate("/components");return}}else t.textContent="New Component",w(e,V);r(),n.addEventListener("input",r)}const V=`<template>
2
2
  <div>Hello, <span>{{name}}</span>.</div>
3
3
  </template>
4
4
  <props>
@@ -24,5 +24,5 @@ ${e.script}
24
24
  ${e.style}
25
25
  </style>`:""].filter(Boolean).join(`
26
26
  `)+`
27
- `}function w(e,n){const o=I(n);c.template?.setValue(o.template),c.props?.setValue(o.props),c.script?.setValue(o.script),c.style?.setValue(o.style)}function N(e){return M({template:c.template?.getValue()??"",props:c.props?.getValue()??"",script:c.script?.getValue()??"",style:c.style?.getValue()??""})}const O=["template","props","script","style"];function A(e){const n=()=>{b&&clearTimeout(b),b=setTimeout(()=>v(e),300)};for(const o of O){const t=e.find(`#component-src-${o}`).get(0);t&&(c[o]=S(t,{initialValue:"",onChange:n}))}}async function v(e){const n=(e.find("#component-name").val()||"").trim()||"preview",o=N(e);g(e,"compiling");let t;try{t=await u.components.compile(n,o)}catch(s){g(e,"error",s.message||"Compile request failed.");return}if(t.errors?.length){const s=t.errors.map(a=>`[${a.type}] ${a.message}`).join(`
28
- `);g(e,"error",s),f=null;return}f=t.js,g(e,"ok"),P(e),B(e,{compiledJs:f,tagName:`dm-${n}`,props:l})}let y=null;function B(e,n){const o=e.find("#component-preview-iframe").get(0);if(!o)return;if(!o.dataset.dmMounted){o.dataset.dmMounted="true",C(e,{type:"mount",payload:n});return}y=n;const t=()=>{o.removeEventListener("load",t),y&&(C(e,{type:"mount",payload:y}),y=null)};o.addEventListener("load",t),o.src=o.src}function g(e,n,o=""){const t=e.find("#component-compile-indicator").get(0),s=e.find("#component-compile-status").get(0),a=e.find("#component-compile-errors").get(0),r=e.find("#save-component-btn").get(0);n==="compiling"?(t.style.background="#888",s.textContent="Compiling\u2026",a.hidden=!0,r&&(r.disabled=!0)):n==="ok"?(t.style.background="#3bb273",s.textContent="Compile OK",a.hidden=!0,r&&(r.disabled=!1)):(t.style.background="#c92a2a",s.textContent="Compile failed \u2014 see below",a.hidden=!1,a.textContent=o,r&&(r.disabled=!0))}function j(e){for(;e.firstChild;)e.removeChild(e.firstChild)}function k(e,n,o=!1){j(e);const t=document.createElement("p");if(t.className="text-muted",t.style.cssText="font-size:.8rem;margin:0;",o){t.appendChild(document.createTextNode("Invalid "));const s=document.createElement("code");s.textContent="<props>",t.appendChild(s),t.appendChild(document.createTextNode(" JSON \u2014 fix the tab above to see the props panel."))}else t.textContent=n;e.appendChild(t)}function P(e){const n=e.find("#component-preview-props-panel").get(0);if(!n)return;let o={};try{o=JSON.parse(c.props?.getValue()||"{}")}catch{k(n,"",!0);return}const t={};j(n);const s=Object.entries(o);if(s.length===0){k(n,"No props declared."),l={};return}for(const[a,r]of s){const i=document.createElement("div");i.style.cssText="display:flex;align-items:center;gap:.5rem;margin-bottom:.5rem;";const m=document.createElement("label");m.style.cssText="font-size:.75rem;color:var(--dm-text-muted,#aaa);min-width:120px;",m.textContent=r.label||a,i.appendChild(m);const d=F(r,a,()=>{l[a]=J(d,r),C(e,{type:"update",payload:{props:l}})}),x=a in l?l[a]:r.default!==void 0?r.default:_(r.type);U(d,r,x),t[a]=x,i.appendChild(d),n.appendChild(i)}l=t}function F(e,n,o){let t;return e.type==="number"?(t=document.createElement("input"),t.type="number",t.className="form-input form-input--sm"):e.type==="boolean"?(t=document.createElement("input"),t.type="checkbox",t.className="form-check"):e.type==="array"||e.type==="object"?(t=document.createElement("textarea"),t.rows=2,t.className="form-input form-input--sm",t.style.fontFamily="var(--dm-font-mono, monospace)"):(t=document.createElement("input"),t.type="text",t.className="form-input form-input--sm"),t.style.flex="1",t.dataset.propName=n,t.addEventListener("input",o),t.addEventListener("change",o),t}function J(e,n){if(n.type==="number")return e.value===""?null:Number(e.value);if(n.type==="boolean")return!!e.checked;if(n.type==="array"||n.type==="object")try{return JSON.parse(e.value||"null")}catch{return null}return e.value}function U(e,n,o){n.type==="boolean"?e.checked=!!o:n.type==="array"||n.type==="object"?e.value=JSON.stringify(o??null):e.value=o??""}function _(e){return e==="number"?0:e==="boolean"?!1:e==="array"?[]:e==="object"?{}:""}function C(e,n){const o=e.find("#component-preview-iframe").get(0);o?.contentWindow&&o.contentWindow.postMessage(n,"*")}function z(e){const n=e.find("#save-component-btn").get(0),o=e.find("#export-component-btn").get(0);n&&n.addEventListener("click",()=>q(e)),o&&o.addEventListener("click",async()=>{if(p)try{await u.components.exportBundle(p)}catch(t){E.toast(t.message||"Export failed.",{type:"error"})}})}async function q(e){const n=(e.find("#component-name").val()||"").trim();if(!n){E.toast("Component name is required.",{type:"warning"});return}if(!/^[a-z][a-z0-9-]*$/.test(n)){E.toast("Name must start with a letter and contain only lowercase letters, digits, and hyphens.",{type:"warning"});return}const o=N(e),t=!!e.find("#component-bundled").is(":checked"),s={...h||{}};delete s.bundled;const a=(e.find("#component-project").val()||"").trim();a?s.project=a:delete s.project;const r=e.find("#save-component-btn").get(0);r&&(r.disabled=!0);try{await u.components.put(n,{source:o,bundled:t,meta:s}),E.toast(p?"Component updated.":"Component created.",{type:"success"}),p||(p=n,R.navigate(`/components/edit/${encodeURIComponent(n)}`))}catch(i){E.toast(i.message||"Failed to save component.",{type:"error"})}finally{r&&(r.disabled=!1)}}async function D(e){if(p)try{const n=await u.components.list(),o=e.find("#component-quick-switch").get(0);if(!o||!n?.length)return;const t=[...n].sort((s,a)=>s.name.localeCompare(a.name));for(const s of t){const a=document.createElement("option");a.value=s.name,a.textContent=s.name,s.name===p&&(a.selected=!0),o.appendChild(a)}o.style.display="",o.addEventListener("change",()=>{const s=o.value;s&&s!==p&&R.navigate(`/components/edit/${encodeURIComponent(s)}`)})}catch{}}
27
+ `}function w(e,n){const o=I(n);c.template?.setValue(o.template),c.props?.setValue(o.props),c.script?.setValue(o.script),c.style?.setValue(o.style)}function N(e){return M({template:c.template?.getValue()??"",props:c.props?.getValue()??"",script:c.script?.getValue()??"",style:c.style?.getValue()??""})}const O=["template","props","script","style"];function A(e){const n=()=>{b&&clearTimeout(b),b=setTimeout(()=>h(e),300)};for(const o of O){const t=e.find(`#component-src-${o}`).get(0);t&&(c[o]=S(t,{initialValue:"",onChange:n}))}}async function h(e){const n=(e.find("#component-name").val()||"").trim()||"preview",o=N(e);g(e,"compiling");let t;try{t=await u.components.compile(n,o)}catch(s){g(e,"error",s.message||"Compile request failed.");return}if(t.errors?.length){const s=t.errors.map(r=>`[${r.type}] ${r.message}`).join(`
28
+ `);g(e,"error",s),f=null;return}f=t.js,g(e,"ok"),P(e),B(e,{compiledJs:f,tagName:`dm-${n}`,props:l})}let y=null;function B(e,n){const o=e.find("#component-preview-iframe").get(0);if(!o)return;if(!o.dataset.dmMounted){o.dataset.dmMounted="true",C(e,{type:"mount",payload:n});return}y=n;const t=()=>{o.removeEventListener("load",t),y&&(C(e,{type:"mount",payload:y}),y=null)};o.addEventListener("load",t),o.src=o.src}function g(e,n,o=""){const t=e.find("#component-compile-indicator").get(0),s=e.find("#component-compile-status").get(0),r=e.find("#component-compile-errors").get(0),a=e.find("#save-component-btn").get(0);n==="compiling"?(t.style.background="var(--dm-text-muted)",s.textContent="Compiling\u2026",r.hidden=!0,a&&(a.disabled=!0)):n==="ok"?(t.style.background="var(--dm-success)",s.textContent="Compile OK",r.hidden=!0,a&&(a.disabled=!1)):(t.style.background="var(--dm-danger)",s.textContent="Compile failed \u2014 see below",r.hidden=!1,r.textContent=o,a&&(a.disabled=!0))}function j(e){for(;e.firstChild;)e.removeChild(e.firstChild)}function k(e,n,o=!1){j(e);const t=document.createElement("p");if(t.className="text-muted",t.style.cssText="font-size:.8rem;margin:0;",o){t.appendChild(document.createTextNode("Invalid "));const s=document.createElement("code");s.textContent="<props>",t.appendChild(s),t.appendChild(document.createTextNode(" JSON \u2014 fix the tab above to see the props panel."))}else t.textContent=n;e.appendChild(t)}function P(e){const n=e.find("#component-preview-props-panel").get(0);if(!n)return;let o={};try{o=JSON.parse(c.props?.getValue()||"{}")}catch{k(n,"",!0);return}const t={};j(n);const s=Object.entries(o);if(s.length===0){k(n,"No props declared."),l={};return}for(const[r,a]of s){const i=document.createElement("div");i.style.cssText="display:flex;align-items:center;gap:.5rem;margin-bottom:.5rem;";const m=document.createElement("label");m.style.cssText="font-size:.75rem;color:var(--dm-text-muted,#aaa);min-width:120px;",m.textContent=a.label||r,i.appendChild(m);const d=F(a,r,()=>{l[r]=J(d,a),C(e,{type:"update",payload:{props:l}})}),x=r in l?l[r]:a.default!==void 0?a.default:_(a.type);U(d,a,x),t[r]=x,i.appendChild(d),n.appendChild(i)}l=t}function F(e,n,o){let t;return e.type==="number"?(t=document.createElement("input"),t.type="number",t.className="form-input form-input--sm"):e.type==="boolean"?(t=document.createElement("input"),t.type="checkbox",t.className="form-check"):e.type==="array"||e.type==="object"?(t=document.createElement("textarea"),t.rows=2,t.className="form-input form-input--sm",t.style.fontFamily="var(--dm-font-mono, monospace)"):(t=document.createElement("input"),t.type="text",t.className="form-input form-input--sm"),t.style.flex="1",t.dataset.propName=n,t.addEventListener("input",o),t.addEventListener("change",o),t}function J(e,n){if(n.type==="number")return e.value===""?null:Number(e.value);if(n.type==="boolean")return!!e.checked;if(n.type==="array"||n.type==="object")try{return JSON.parse(e.value||"null")}catch{return null}return e.value}function U(e,n,o){n.type==="boolean"?e.checked=!!o:n.type==="array"||n.type==="object"?e.value=JSON.stringify(o??null):e.value=o??""}function _(e){return e==="number"?0:e==="boolean"?!1:e==="array"?[]:e==="object"?{}:""}function C(e,n){const o=e.find("#component-preview-iframe").get(0);o?.contentWindow&&o.contentWindow.postMessage(n,"*")}function z(e){const n=e.find("#save-component-btn").get(0),o=e.find("#export-component-btn").get(0);n&&n.addEventListener("click",()=>q(e)),o&&o.addEventListener("click",async()=>{if(p)try{await u.components.exportBundle(p)}catch(t){E.toast(t.message||"Export failed.",{type:"error"})}})}async function q(e){const n=(e.find("#component-name").val()||"").trim();if(!n){E.toast("Component name is required.",{type:"warning"});return}if(!/^[a-z][a-z0-9-]*$/.test(n)){E.toast("Name must start with a letter and contain only lowercase letters, digits, and hyphens.",{type:"warning"});return}const o=N(e),t=!!e.find("#component-bundled").is(":checked"),s={...v||{}};delete s.bundled;const r=(e.find("#component-project").val()||"").trim();r?s.project=r:delete s.project;const a=e.find("#save-component-btn").get(0);a&&(a.disabled=!0);try{await u.components.put(n,{source:o,bundled:t,meta:s}),E.toast(p?"Component updated.":"Component created.",{type:"success"}),p||(p=n,R.navigate(`/components/edit/${encodeURIComponent(n)}`))}catch(i){E.toast(i.message||"Failed to save component.",{type:"error"})}finally{a&&(a.disabled=!1)}}async function D(e){if(p)try{const n=await u.components.list(),o=e.find("#component-quick-switch").get(0);if(!o||!n?.length)return;const t=[...n].sort((s,r)=>s.name.localeCompare(r.name));for(const s of t){const r=document.createElement("option");r.value=s.name,r.textContent=s.name,s.name===p&&(r.selected=!0),o.appendChild(r)}o.style.display="",o.addEventListener("change",()=>{const s=o.value;s&&s!==p&&R.navigate(`/components/edit/${encodeURIComponent(s)}`)})}catch{}}