domma-cms 0.36.2 → 0.36.4

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 (40) hide show
  1. package/admin/css/admin.css +1 -1
  2. package/admin/js/lib/card-builder.js +3 -3
  3. package/admin/js/lib/editor-folding.js +1 -1
  4. package/admin/js/lib/effects-builder.js +2 -2
  5. package/admin/js/lib/markdown-toolbar.js +3 -3
  6. package/admin/js/lib/shortcode-context-menu.js +4 -4
  7. package/admin/js/lib/shortcode-modal.js +1 -1
  8. package/admin/js/lib/shortcode-registry.js +1 -1
  9. package/admin/js/lib/timeline-builder.js +2 -2
  10. package/admin/js/templates/form-editor.html +17 -16
  11. package/admin/js/views/actions-list.js +1 -1
  12. package/admin/js/views/api-endpoint-editor.js +3 -3
  13. package/admin/js/views/api-endpoints.js +2 -2
  14. package/admin/js/views/api-tokens.js +4 -4
  15. package/admin/js/views/blocks.js +5 -5
  16. package/admin/js/views/collection-editor.js +4 -4
  17. package/admin/js/views/collection-entries.js +1 -1
  18. package/admin/js/views/component-editor.js +1 -1
  19. package/admin/js/views/components.js +2 -2
  20. package/admin/js/views/effects.js +1 -1
  21. package/admin/js/views/form-editor.js +7 -7
  22. package/admin/js/views/form-submissions.js +1 -1
  23. package/admin/js/views/forms.js +1 -1
  24. package/admin/js/views/layouts.js +1 -1
  25. package/admin/js/views/login.js +1 -1
  26. package/admin/js/views/menu-editor.js +8 -8
  27. package/admin/js/views/menu-locations.js +1 -1
  28. package/admin/js/views/menus.js +3 -3
  29. package/admin/js/views/my-profile.js +1 -1
  30. package/admin/js/views/notifications.js +1 -1
  31. package/admin/js/views/page-editor.js +29 -29
  32. package/admin/js/views/project-detail.js +1 -1
  33. package/admin/js/views/project-editor.js +1 -1
  34. package/admin/js/views/project-settings.js +1 -1
  35. package/admin/js/views/projects.js +2 -2
  36. package/admin/js/views/user-editor.js +1 -1
  37. package/admin/js/views/view-editor.js +1 -1
  38. package/admin/js/views/view-preview.js +1 -1
  39. package/admin/js/views/views-list.js +1 -1
  40. package/package.json +1 -1
@@ -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"]],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
+ import{api as _}from"../api.js";const pe=[["_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"]],we=["id","createdBy"],Se=/\{\{\s*(params|query)\.([a-zA-Z_][a-zA-Z0-9_]*)\s*\}\}/g;function qe(x){const v=new Set;for(const n of Object.values(x?.filter||{}))if(typeof n=="string")for(const N of n.matchAll(Se))N[1]==="query"&&v.add(N[2]);return[...v]}function Ae(x){return String(x??"").replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")}let O=null;function ke(x){for(const[v]of pe)if(x.endsWith(v))return{field:x.slice(0,-v.length),op:v};return{field:x,op:"_eq"}}export const apiEndpointEditorView={templateUrl:"/admin/js/templates/api-endpoint-editor.html",async onMount(x){const v=x.get(0),n=e=>v.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 G=E.loader(v,{type:"dots"}),[ue,me,fe,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){G.destroy(),E.toast("Endpoint not found.",{type:"error"}),location.hash="#/api-endpoints";return}const ye=me.filter(e=>!e.systemManaged&&!e.preset),he=(fe.entries||[]).map(e=>e.data?.name).filter(Boolean),k=n("endpoint-project");for(const e of ue){const t=document.createElement("option");t.value=e.slug,t.textContent=`${e.name} (${e.slug})`,k.appendChild(t)}const y=n("ep-collection");function ee(e){return e.meta&&e.meta.project||"core"}function z(e){const t=e!==void 0?e:y.value,r=k.value,o=ye.filter(a=>{const s=ee(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})${ee(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 he){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{}te();for(const t of K.querySelectorAll(".ep-filter-row"))U(t.querySelector(".ep-f-field"));ne()}function F(){return[...T,...we]}function te(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 ne(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 pe){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 ve(){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:ve(),sort:n("ep-sort").value||null,order:n("ep-order").value,limit:parseInt(n("ep-limit").value,10)||0,fields:[...v.querySelectorAll(".ep-field-cb:checked")].map(e=>e.value),enabled:n("ep-enabled").checked}}let B=null,g=null;function D(){return!B||JSON.stringify(I())!==B}function h(){oe(),Z()}function oe(){n("ep-url-preview").textContent=`/api/x/${k.value||"<project>"}${n("ep-path").value||"/\u2026"}`}function ge(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=g?.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 ge(g?.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 qe(g)){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"}`,Ee=12;function ae(){const e=S.get(W);return Array.isArray(e)?e:[]}function re(e){const t=ae().filter(o=>o.url!==e.url),r=[e,...t].slice(0,Ee);S.set(W,r),Y()}function Ce(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=ae();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/${g?.project||""}`,"")||o.url,d.title=o.url,a.appendChild(s),a.appendChild(d),a.addEventListener("click",()=>Ce(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 se(e,t){return t?f(m.index,String(e)):f(m.key,JSON.stringify(String(e)))}function xe(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 ie(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(se(t,r)),p.appendChild(f(m.punc,": "))),p.appendChild(o?f(m.punc,Array.isArray(e)?"[]":"{}"):xe(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(se(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(ie(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 C=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 C(!1),i.addEventListener("click",()=>C(!l)),d}let V="";function le(e){n("tryit-json").style.display=e?"":"none",n("tryit-body").style.display=e?"none":"",n("tryit-json-toggle").textContent=e?"Raw":"Tree"}function ce(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(ie(r)),n("tryit-json-toggle").style.display=o?"":"none",le(o)}async function Q(){if(!g)return;const e={},t={};let r=g.path;for(const l of n("tryit-params").querySelectorAll("input")){e[l.dataset.param]=l.value;const C=new RegExp(":"+l.dataset.param.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")+"(?=/|$)","g");r=r.replace(C,encodeURIComponent(l.value))}const o=[];for(const l of n("tryit-query-params").querySelectorAll("input")){const C=l.value.trim();t[l.dataset.queryParam]=l.value,C&&o.push(`${encodeURIComponent(l.dataset.queryParam)}=${encodeURIComponent(C)}`)}const a=n("tryit-query").value.trim();a&&o.push(a);const s=o.join("&"),c=`/api/x/${g.project}${r}${s?"?"+s:""}`,d={ts:Date.now(),url:c,params:e,queryParams:t,extra:a},i={},j=n("tryit-token").value.trim();g.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,C]of Object.entries(i))A.push(` -H ${P(`${l}: ${C}`)}`);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}),C=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,be)=>b.push(`${be}: ${X}`)),n("tryit-headers-group").style.display="",n("tryit-headers").textContent=b.length?b.join(`
3
+ `):"(none exposed)",ce(C),re({...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",ce(String(l.message||l)),re({...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(),te(u.fields||[]),ne(u.sort||"");for(const[e,t]of Object.entries(u.filter||{})){const{field:r,op:o}=ke(e);M(r,o,String(t))}g=u,B=JSON.stringify(I())}else await R(),M();oe(),Z(),G.destroy();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",()=>{le(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 de(){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)g=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",de);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(),de()):r==="enter"&&(e.preventDefault(),g&&!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(v)}};
@@ -1,7 +1,7 @@
1
- import{api as s}from"../api.js";import{filterByProject as l,getProjectFromHash as p}from"../lib/project-context.js";function n(a){return String(a??"").replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")}function c(a){return a==="public"?'<span class="badge badge-warning">Public</span>':a==="token"?'<span class="badge badge-info">Token</span>':`<span class="badge badge-secondary">${n(a)}</span>`}export const apiEndpointsView={templateUrl:"/admin/js/templates/api-endpoints.html",async onMount(a){let i=await s.apiEndpoints.list().catch(()=>[]);const d=p();d&&(i=l(i,d)),T.create("#api-endpoints-table",{data:i,emptyMessage:'No endpoints yet. Click "New endpoint" to build one.',columns:[{key:"name",title:"Name",sortable:!0},{key:"path",title:"URL",render:(e,t)=>`<code>/api/x/${n(t.project)}${n(t.path)}</code>`},{key:"collection",title:"Collection",sortable:!0,render:e=>`<code>${n(e)}</code>`},{key:"mode",title:"Mode",render:e=>n(e)},{key:"auth",title:"Auth",render:e=>c(e)},{key:"enabled",title:"Status",render:e=>e?'<span class="badge badge-success">Enabled</span>':'<span class="badge badge-secondary">Disabled</span>'},{key:"_actions",title:"Actions",render:(e,t)=>`
1
+ import{api as d}from"../api.js";import{filterByProject as p,getProjectFromHash as c}from"../lib/project-context.js";function n(a){return String(a??"").replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")}function r(a){return a==="public"?'<span class="badge badge-warning">Public</span>':a==="token"?'<span class="badge badge-info">Token</span>':`<span class="badge badge-secondary">${n(a)}</span>`}export const apiEndpointsView={templateUrl:"/admin/js/templates/api-endpoints.html",async onMount(a){const l=E.loader(a.get(0),{type:"dots"});let i=await d.apiEndpoints.list().catch(()=>[]);l.destroy();const s=c();s&&(i=p(i,s)),T.create("#api-endpoints-table",{data:i,emptyMessage:'No endpoints yet. Click "New endpoint" to build one.',columns:[{key:"name",title:"Name",sortable:!0},{key:"path",title:"URL",render:(e,t)=>`<code>/api/x/${n(t.project)}${n(t.path)}</code>`},{key:"collection",title:"Collection",sortable:!0,render:e=>`<code>${n(e)}</code>`},{key:"mode",title:"Mode",render:e=>n(e)},{key:"auth",title:"Auth",render:e=>r(e)},{key:"enabled",title:"Status",render:e=>e?'<span class="badge badge-success">Enabled</span>':'<span class="badge badge-secondary">Disabled</span>'},{key:"_actions",title:"Actions",render:(e,t)=>`
2
2
  <span style="display:inline-flex;gap:0.25rem;">
3
3
  <a class="btn btn-sm btn-ghost" href="#/api-endpoints/edit/${encodeURIComponent(t.id)}" data-tooltip="Edit"><span data-icon="edit"></span></a>
4
4
  <button class="btn btn-sm btn-ghost btn-toggle-endpoint" data-id="${n(t.id)}" data-enabled="${t.enabled?"1":""}" data-tooltip="${t.enabled?"Disable":"Enable"}"><span data-icon="${t.enabled?"pause":"play"}"></span></button>
5
5
  <button class="btn btn-sm btn-danger btn-delete-endpoint" data-id="${n(t.id)}" data-name="${n(t.name)}" data-tooltip="Delete"><span data-icon="trash"></span></button>
6
6
  </span>
7
- `}]}),Domma.icons.scan(a.get(0)),document.querySelectorAll("#api-endpoints-table [data-tooltip]").forEach(e=>{E.tooltip(e,{content:e.getAttribute("data-tooltip"),position:"top"})}),a.off("click",".btn-toggle-endpoint").on("click",".btn-toggle-endpoint",async function(){const e=$(this).data("id"),t=!!$(this).data("enabled");try{await s.apiEndpoints.update(e,{enabled:!t}),E.toast(`Endpoint ${t?"disabled":"enabled"}.`,{type:"success"}),location.reload()}catch(o){E.toast(`Update failed: ${o.message||o}`,{type:"error"})}}),a.off("click",".btn-delete-endpoint").on("click",".btn-delete-endpoint",async function(){const e=$(this).data("id"),t=$(this).data("name");if(await E.confirm(`Delete endpoint "${t}"? External callers using it will immediately get 404s. This cannot be undone.`))try{await s.apiEndpoints.remove(e),E.toast("Endpoint deleted.",{type:"success"}),location.reload()}catch(o){E.toast(`Delete failed: ${o.message||o}`,{type:"error"})}})}};
7
+ `}]}),Domma.icons.scan(a.get(0)),document.querySelectorAll("#api-endpoints-table [data-tooltip]").forEach(e=>{E.tooltip(e,{content:e.getAttribute("data-tooltip"),position:"top"})}),a.off("click",".btn-toggle-endpoint").on("click",".btn-toggle-endpoint",async function(){const e=$(this).data("id"),t=!!$(this).data("enabled");try{await d.apiEndpoints.update(e,{enabled:!t}),E.toast(`Endpoint ${t?"disabled":"enabled"}.`,{type:"success"}),location.reload()}catch(o){E.toast(`Update failed: ${o.message||o}`,{type:"error"})}}),a.off("click",".btn-delete-endpoint").on("click",".btn-delete-endpoint",async function(){const e=$(this).data("id"),t=$(this).data("name");if(await E.confirm(`Delete endpoint "${t}"? External callers using it will immediately get 404s. This cannot be undone.`))try{await d.apiEndpoints.remove(e),E.toast("Endpoint deleted.",{type:"success"}),location.reload()}catch(o){E.toast(`Delete failed: ${o.message||o}`,{type:"error"})}})}};
@@ -1,8 +1,8 @@
1
- import{api as m}from"../api.js";function d(o){return String(o??"").replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")}function f(o){return!Array.isArray(o)||o.length===0?'<span class="text-muted">All collections in project</span>':d(o.map(r=>{const s=Array.isArray(r.verbs)&&r.verbs.length?r.verbs.join(","):"all";return`${r.collection}: ${s}`}).join("; "))}function g(o){const r=[];for(const s of(o||"").split(`
2
- `)){const n=s.trim();if(!n)continue;const[e,t]=n.split(":").map(l=>l.trim());if(!e)return{error:`Invalid scope line: "${n}"`};const a={collection:e};if(t){const l=t.split(",").map(c=>c.trim()).filter(Boolean),i=l.find(c=>!["create","read","update","delete"].includes(c));if(i)return{error:`Unknown verb "${i}" in scope "${n}"`};a.verbs=l}r.push(a)}return{scopes:r}}function b(o,r,s){const n=document.createElement("div");n.style.marginBottom=".75rem";const e=document.createElement("label");if(e.className="form-label",e.textContent=o,n.appendChild(e),n.appendChild(r),s){const t=document.createElement("p");t.className="text-muted",t.style.cssText="font-size:.8rem;margin:.25rem 0 0;",t.textContent=s,n.appendChild(t)}return n}function h(o,r){const s=E.modal({title:"Token created",size:"sm"}),n=document.createElement("div");n.style.padding="1rem";const e=document.createElement("p");e.innerHTML="<strong>Copy this token now.</strong> It is shown once and cannot be recovered \u2014 only a hash is stored.",e.style.marginBottom=".75rem",n.appendChild(e);const t=document.createElement("code");t.textContent=o,t.style.cssText="display:block;padding:.6rem;word-break:break-all;user-select:all;margin-bottom:.75rem;",n.appendChild(t);const a=document.createElement("button");a.className="btn btn-primary",a.textContent="Copy to clipboard",a.style.marginRight=".5rem",a.addEventListener("click",async()=>{try{await navigator.clipboard.writeText(o),a.textContent="Copied!"}catch{E.toast("Copy failed \u2014 select the token text manually.",{type:"error"})}}),n.appendChild(a);const l=document.createElement("button");l.className="btn btn-secondary",l.textContent="Done",l.addEventListener("click",()=>{s.close(),r()}),n.appendChild(l),s.element.appendChild(n),s.open()}function v(o,r){const s=E.modal({title:"New API Token",size:"md"}),n=document.createElement("div");n.style.padding=".5rem 1rem 1rem";const e=document.createElement("input");e.type="text",e.className="form-input",e.placeholder="e.g. mobile-app, ci-pipeline";const t=document.createElement("select");t.className="form-input";for(const c of o){const p=document.createElement("option");p.value=c.slug,p.textContent=`${c.name} (${c.slug})`,t.appendChild(p)}const a=document.createElement("input");a.type="datetime-local",a.className="form-input";const l=document.createElement("textarea");l.className="form-input",l.rows=3,l.placeholder=`jobs: read
3
- enquiries: create, read`,n.appendChild(b("Name",e)),n.appendChild(b("Project",t,"The token only works on collections in this project.")),n.appendChild(b("Expires (optional)",a,"Leave empty for a non-expiring token.")),n.appendChild(b("Scopes (optional)",l,'One per line: "collection: verb, verb". Leave empty to allow every collection in the project.'));const i=document.createElement("button");i.className="btn btn-primary",i.textContent="Create token",i.addEventListener("click",async()=>{const c=e.value.trim();if(!c){E.toast("A token name is required.",{type:"error"});return}const{scopes:p,error:y}=g(l.value);if(y){E.toast(y,{type:"error"});return}const k={name:c,project:t.value,scopes:p,expiresAt:a.value?new Date(a.value).toISOString():null};try{i.disabled=!0;const{plaintext:u}=await m.apiTokens.create(k);s.close(),h(u,r)}catch(u){i.disabled=!1,E.toast(`Create failed: ${u.message||u}`,{type:"error"})}}),n.appendChild(i),s.element.appendChild(n),s.open(),setTimeout(()=>e.focus(),100)}export const apiTokensView={templateUrl:"/admin/js/templates/api-tokens.html",async onMount(o){const[r,s]=await Promise.all([m.apiTokens.list().catch(()=>[]),m.projects.list().catch(()=>[])]);T.create("#api-tokens-table",{data:r,emptyMessage:'No API tokens yet. Click "New token" to create one.',columns:[{key:"name",title:"Name",sortable:!0},{key:"project",title:"Project",sortable:!0,render:e=>`<code>${d(e)}</code>`},{key:"tokenHint",title:"Token",render:e=>`<code>dcms_\u2026${d(e||"????")}</code>`},{key:"scopes",title:"Scopes",render:e=>f(e)},{key:"enabled",title:"Status",render:(e,t)=>t.expiresAt&&Date.parse(t.expiresAt)<Date.now()?'<span class="badge badge-warning">Expired</span>':e?'<span class="badge badge-success">Enabled</span>':'<span class="badge badge-secondary">Disabled</span>'},{key:"expiresAt",title:"Expires",render:e=>e?D(e).format("D MMM YYYY, HH:mm"):'<span class="text-muted">\u2014</span>'},{key:"lastUsedAt",title:"Last used",render:e=>e?D(e).format("D MMM YYYY, HH:mm"):'<span class="text-muted">Never</span>'},{key:"_actions",title:"Actions",render:(e,t)=>`
1
+ import{api as m}from"../api.js";function d(a){return String(a??"").replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")}function f(a){return!Array.isArray(a)||a.length===0?'<span class="text-muted">All collections in project</span>':d(a.map(l=>{const r=Array.isArray(l.verbs)&&l.verbs.length?l.verbs.join(","):"all";return`${l.collection}: ${r}`}).join("; "))}function g(a){const l=[];for(const r of(a||"").split(`
2
+ `)){const n=r.trim();if(!n)continue;const[o,e]=n.split(":").map(s=>s.trim());if(!o)return{error:`Invalid scope line: "${n}"`};const t={collection:o};if(e){const s=e.split(",").map(c=>c.trim()).filter(Boolean),i=s.find(c=>!["create","read","update","delete"].includes(c));if(i)return{error:`Unknown verb "${i}" in scope "${n}"`};t.verbs=s}l.push(t)}return{scopes:l}}function b(a,l,r){const n=document.createElement("div");n.style.marginBottom=".75rem";const o=document.createElement("label");if(o.className="form-label",o.textContent=a,n.appendChild(o),n.appendChild(l),r){const e=document.createElement("p");e.className="text-muted",e.style.cssText="font-size:.8rem;margin:.25rem 0 0;",e.textContent=r,n.appendChild(e)}return n}function h(a,l){const r=E.modal({title:"Token created",size:"sm"}),n=document.createElement("div");n.style.padding="1rem";const o=document.createElement("p");o.innerHTML="<strong>Copy this token now.</strong> It is shown once and cannot be recovered \u2014 only a hash is stored.",o.style.marginBottom=".75rem",n.appendChild(o);const e=document.createElement("code");e.textContent=a,e.style.cssText="display:block;padding:.6rem;word-break:break-all;user-select:all;margin-bottom:.75rem;",n.appendChild(e);const t=document.createElement("button");t.className="btn btn-primary",t.textContent="Copy to clipboard",t.style.marginRight=".5rem",t.addEventListener("click",async()=>{try{await navigator.clipboard.writeText(a),t.textContent="Copied!"}catch{E.toast("Copy failed \u2014 select the token text manually.",{type:"error"})}}),n.appendChild(t);const s=document.createElement("button");s.className="btn btn-secondary",s.textContent="Done",s.addEventListener("click",()=>{r.close(),l()}),n.appendChild(s),r.element.appendChild(n),r.open()}function v(a,l){const r=E.modal({title:"New API Token",size:"md"}),n=document.createElement("div");n.style.padding=".5rem 1rem 1rem";const o=document.createElement("input");o.type="text",o.className="form-input",o.placeholder="e.g. mobile-app, ci-pipeline";const e=document.createElement("select");e.className="form-input";for(const c of a){const p=document.createElement("option");p.value=c.slug,p.textContent=`${c.name} (${c.slug})`,e.appendChild(p)}const t=document.createElement("input");t.type="datetime-local",t.className="form-input";const s=document.createElement("textarea");s.className="form-input",s.rows=3,s.placeholder=`jobs: read
3
+ enquiries: create, read`,n.appendChild(b("Name",o)),n.appendChild(b("Project",e,"The token only works on collections in this project.")),n.appendChild(b("Expires (optional)",t,"Leave empty for a non-expiring token.")),n.appendChild(b("Scopes (optional)",s,'One per line: "collection: verb, verb". Leave empty to allow every collection in the project.'));const i=document.createElement("button");i.className="btn btn-primary",i.textContent="Create token",i.addEventListener("click",async()=>{const c=o.value.trim();if(!c){E.toast("A token name is required.",{type:"error"});return}const{scopes:p,error:y}=g(s.value);if(y){E.toast(y,{type:"error"});return}const k={name:c,project:e.value,scopes:p,expiresAt:t.value?new Date(t.value).toISOString():null};try{i.disabled=!0;const{plaintext:u}=await m.apiTokens.create(k);r.close(),h(u,l)}catch(u){i.disabled=!1,E.toast(`Create failed: ${u.message||u}`,{type:"error"})}}),n.appendChild(i),r.element.appendChild(n),r.open(),setTimeout(()=>o.focus(),100)}export const apiTokensView={templateUrl:"/admin/js/templates/api-tokens.html",async onMount(a){const l=E.loader(a.get(0),{type:"dots"}),[r,n]=await Promise.all([m.apiTokens.list().catch(()=>[]),m.projects.list().catch(()=>[])]);l.destroy(),T.create("#api-tokens-table",{data:r,emptyMessage:'No API tokens yet. Click "New token" to create one.',columns:[{key:"name",title:"Name",sortable:!0},{key:"project",title:"Project",sortable:!0,render:e=>`<code>${d(e)}</code>`},{key:"tokenHint",title:"Token",render:e=>`<code>dcms_\u2026${d(e||"????")}</code>`},{key:"scopes",title:"Scopes",render:e=>f(e)},{key:"enabled",title:"Status",render:(e,t)=>t.expiresAt&&Date.parse(t.expiresAt)<Date.now()?'<span class="badge badge-warning">Expired</span>':e?'<span class="badge badge-success">Enabled</span>':'<span class="badge badge-secondary">Disabled</span>'},{key:"expiresAt",title:"Expires",render:e=>e?D(e).format("D MMM YYYY, HH:mm"):'<span class="text-muted">\u2014</span>'},{key:"lastUsedAt",title:"Last used",render:e=>e?D(e).format("D MMM YYYY, HH:mm"):'<span class="text-muted">Never</span>'},{key:"_actions",title:"Actions",render:(e,t)=>`
4
4
  <span style="display:inline-flex;gap:0.25rem;">
5
5
  <button class="btn btn-sm btn-ghost btn-toggle-token" data-id="${d(t.id)}" data-enabled="${t.enabled?"1":""}" data-tooltip="${t.enabled?"Disable":"Enable"}"><span data-icon="${t.enabled?"pause":"play"}"></span></button>
6
6
  <button class="btn btn-sm btn-danger btn-revoke-token" data-id="${d(t.id)}" data-name="${d(t.name)}" data-tooltip="Revoke"><span data-icon="trash"></span></button>
7
7
  </span>
8
- `}]}),Domma.icons.scan(o.get(0)),document.querySelectorAll("#api-tokens-table [data-tooltip]").forEach(e=>{E.tooltip(e,{content:e.getAttribute("data-tooltip"),position:"top"})});const n=()=>location.reload();o.off("click","#btn-new-token").on("click","#btn-new-token",()=>{if(!s.length){E.toast("No projects available \u2014 create a project first.",{type:"error"});return}v(s,n)}),o.off("click",".btn-toggle-token").on("click",".btn-toggle-token",async function(){const e=$(this).data("id"),t=!!$(this).data("enabled");try{await m.apiTokens.update(e,{enabled:!t}),E.toast(`Token ${t?"disabled":"enabled"}.`,{type:"success"}),n()}catch(a){E.toast(`Update failed: ${a.message||a}`,{type:"error"})}}),o.off("click",".btn-revoke-token").on("click",".btn-revoke-token",async function(){const e=$(this).data("id"),t=$(this).data("name");if(await E.confirm(`Revoke token "${t}"? External callers using it will immediately lose access. This cannot be undone.`))try{await m.apiTokens.revoke(e),E.toast("Token revoked.",{type:"success"}),n()}catch(a){E.toast(`Revoke failed: ${a.message||a}`,{type:"error"})}})}};
8
+ `}]}),Domma.icons.scan(a.get(0)),document.querySelectorAll("#api-tokens-table [data-tooltip]").forEach(e=>{E.tooltip(e,{content:e.getAttribute("data-tooltip"),position:"top"})});const o=()=>location.reload();a.off("click","#btn-new-token").on("click","#btn-new-token",()=>{if(!n.length){E.toast("No projects available \u2014 create a project first.",{type:"error"});return}v(n,o)}),a.off("click",".btn-toggle-token").on("click",".btn-toggle-token",async function(){const e=$(this).data("id"),t=!!$(this).data("enabled");try{await m.apiTokens.update(e,{enabled:!t}),E.toast(`Token ${t?"disabled":"enabled"}.`,{type:"success"}),o()}catch(s){E.toast(`Update failed: ${s.message||s}`,{type:"error"})}}),a.off("click",".btn-revoke-token").on("click",".btn-revoke-token",async function(){const e=$(this).data("id"),t=$(this).data("name");if(await E.confirm(`Revoke token "${t}"? External callers using it will immediately lose access. This cannot be undone.`))try{await m.apiTokens.revoke(e),E.toast("Token revoked.",{type:"success"}),o()}catch(s){E.toast(`Revoke failed: ${s.message||s}`,{type:"error"})}})}};
@@ -1,5 +1,5 @@
1
- import{api as s}from"../api.js";import{filterByProject as p,getProjectFromHash as m}from"../lib/project-context.js";function c(e){return String(e??"").replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")}function u(e){for(;e.firstChild;)e.removeChild(e.firstChild)}export const blocksView={templateUrl:"/admin/js/templates/blocks.html",async onMount(e){f(),await i(e),b(e),Domma.icons.scan(e.get(0))}};let d=!1;function f(){d||(d=!0,I.register("download",{viewBox:"0 0 24 24",paths:["M12 3v12","M7 10l5 5 5-5","M5 19h14"],stroke:"currentColor",fill:"none",strokeWidth:2,strokeLinecap:"round",strokeLinejoin:"round"}),I.register("upload",{viewBox:"0 0 24 24",paths:["M12 21V9","M7 14l5-5 5 5","M5 5h14"],stroke:"currentColor",fill:"none",strokeWidth:2,strokeLinecap:"round",strokeLinejoin:"round"}),I.register("plus",{viewBox:"0 0 24 24",paths:["M12 5v14","M5 12h14"],stroke:"currentColor",fill:"none",strokeWidth:2,strokeLinecap:"round",strokeLinejoin:"round"}))}async function i(e){const r=e.find("#blocks-table-container").get(0);if(!r)return;let a=[];try{a=await s.blocks.list()}catch(t){u(r);const o=document.createElement("p");o.className="text-muted",o.textContent=`Failed to load blocks: ${t.message}`,r.appendChild(o);return}const n=m();n&&(a=p(a,n)),T.create(r,{data:a,emptyMessage:'No blocks yet. Click "New Block" to create your first template.',columns:[{key:"name",title:"Name",render:t=>`<a href="#/blocks/edit/${c(t)}">${c(t)}.html</a>`},{key:"size",title:"Size",render:t=>`${t} B`},{key:"updatedAt",title:"Updated",render:t=>t?new Date(t).toLocaleString():"\u2014"},{key:"name",title:"Actions",render:t=>{const o=c(t);return`<div style="display:flex;gap:.4rem;justify-content:flex-end;">
2
- <a href="#/blocks/edit/${o}" class="btn btn-sm btn-ghost" data-tooltip="Edit"><span data-icon="edit"></span></a>
3
- <button class="btn btn-sm btn-ghost js-export-block" data-name="${o}" data-tooltip="Export as .dmblock.json"><span data-icon="download"></span></button>
4
- <button class="btn btn-sm btn-danger js-delete-block" data-name="${o}" data-tooltip="Delete"><span data-icon="trash"></span></button>
5
- </div>`}}]}),r.querySelectorAll(".js-delete-block").forEach(t=>{t.addEventListener("click",async()=>{await k(t.dataset.name,e)})}),r.querySelectorAll(".js-export-block").forEach(t=>{t.addEventListener("click",async()=>{try{await s.blocks.exportBundle(t.dataset.name)}catch(o){E.toast(o.message||"Export failed.",{type:"error"})}})}),Domma.icons.scan(r),r.querySelectorAll("[data-tooltip]").forEach(t=>{E.tooltip(t,{content:t.getAttribute("data-tooltip"),position:"top"})})}async function k(e,r){if(await E.confirm(`Delete block "${e}"? This cannot be undone.`))try{await s.blocks.delete(e),E.toast("Block deleted.",{type:"success"}),await i(r)}catch(n){E.toast(n.message||"Failed to delete block.",{type:"error"})}}function b(e){const r=e.find("#import-block-btn").get(0),a=e.find("#import-block-file").get(0);!r||!a||(r.addEventListener("click",()=>a.click()),a.addEventListener("change",async()=>{const n=a.files?.[0];if(!n)return;a.value="";let t;try{const o=await n.text();t=JSON.parse(o)}catch{E.toast("Not a valid .dmblock.json file (could not parse JSON).",{type:"error"});return}if(!t||typeof t!="object"||!t.name||typeof t.html!="string"){E.toast("Bundle is missing a name or html field.",{type:"error"});return}try{const o=await s.blocks.importBundle(t);E.toast(`Imported "${o.name}".`,{type:"success"}),await i(e)}catch(o){if(o.code==="CONFLICT"){if(!await E.confirm(`A block named "${o.name}" already exists. Overwrite it with the imported version?`)){E.toast("Import cancelled.",{type:"info"});return}try{const l=await s.blocks.importBundle(t,{overwrite:!0});E.toast(`Overwrote "${l.name}".`,{type:"success"}),await i(e)}catch(l){E.toast(l.message||"Import failed.",{type:"error"})}return}E.toast(o.message||"Import failed.",{type:"error"})}}))}
1
+ import{api as i}from"../api.js";import{filterByProject as m,getProjectFromHash as u}from"../lib/project-context.js";function d(e){return String(e??"").replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")}function f(e){for(;e.firstChild;)e.removeChild(e.firstChild)}export const blocksView={templateUrl:"/admin/js/templates/blocks.html",async onMount(e){k(),await l(e),y(e),Domma.icons.scan(e.get(0))}};let p=!1;function k(){p||(p=!0,I.register("download",{viewBox:"0 0 24 24",paths:["M12 3v12","M7 10l5 5 5-5","M5 19h14"],stroke:"currentColor",fill:"none",strokeWidth:2,strokeLinecap:"round",strokeLinejoin:"round"}),I.register("upload",{viewBox:"0 0 24 24",paths:["M12 21V9","M7 14l5-5 5 5","M5 5h14"],stroke:"currentColor",fill:"none",strokeWidth:2,strokeLinecap:"round",strokeLinejoin:"round"}),I.register("plus",{viewBox:"0 0 24 24",paths:["M12 5v14","M5 12h14"],stroke:"currentColor",fill:"none",strokeWidth:2,strokeLinecap:"round",strokeLinejoin:"round"}))}async function l(e){const o=e.find("#blocks-table-container").get(0);if(!o)return;let r=[];const n=E.loader(e.get(0),{type:"dots"});try{r=await i.blocks.list()}catch(t){f(o);const s=document.createElement("p");s.className="text-muted",s.textContent=`Failed to load blocks: ${t.message}`,o.appendChild(s);return}finally{n.destroy()}const a=u();a&&(r=m(r,a)),T.create(o,{data:r,emptyMessage:'No blocks yet. Click "New Block" to create your first template.',columns:[{key:"name",title:"Name",render:t=>`<a href="#/blocks/edit/${d(t)}">${d(t)}.html</a>`},{key:"size",title:"Size",render:t=>`${t} B`},{key:"updatedAt",title:"Updated",render:t=>t?new Date(t).toLocaleString():"\u2014"},{key:"name",title:"Actions",render:t=>{const s=d(t);return`<div style="display:flex;gap:.4rem;justify-content:flex-end;">
2
+ <a href="#/blocks/edit/${s}" class="btn btn-sm btn-ghost" data-tooltip="Edit"><span data-icon="edit"></span></a>
3
+ <button class="btn btn-sm btn-ghost js-export-block" data-name="${s}" data-tooltip="Export as .dmblock.json"><span data-icon="download"></span></button>
4
+ <button class="btn btn-sm btn-danger js-delete-block" data-name="${s}" data-tooltip="Delete"><span data-icon="trash"></span></button>
5
+ </div>`}}]}),o.querySelectorAll(".js-delete-block").forEach(t=>{t.addEventListener("click",async()=>{await b(t.dataset.name,e)})}),o.querySelectorAll(".js-export-block").forEach(t=>{t.addEventListener("click",async()=>{try{await i.blocks.exportBundle(t.dataset.name)}catch(s){E.toast(s.message||"Export failed.",{type:"error"})}})}),Domma.icons.scan(o),o.querySelectorAll("[data-tooltip]").forEach(t=>{E.tooltip(t,{content:t.getAttribute("data-tooltip"),position:"top"})})}async function b(e,o){if(await E.confirm(`Delete block "${e}"? This cannot be undone.`))try{await i.blocks.delete(e),E.toast("Block deleted.",{type:"success"}),await l(o)}catch(n){E.toast(n.message||"Failed to delete block.",{type:"error"})}}function y(e){const o=e.find("#import-block-btn").get(0),r=e.find("#import-block-file").get(0);!o||!r||(o.addEventListener("click",()=>r.click()),r.addEventListener("change",async()=>{const n=r.files?.[0];if(!n)return;r.value="";let a;try{const t=await n.text();a=JSON.parse(t)}catch{E.toast("Not a valid .dmblock.json file (could not parse JSON).",{type:"error"});return}if(!a||typeof a!="object"||!a.name||typeof a.html!="string"){E.toast("Bundle is missing a name or html field.",{type:"error"});return}try{const t=await i.blocks.importBundle(a);E.toast(`Imported "${t.name}".`,{type:"success"}),await l(e)}catch(t){if(t.code==="CONFLICT"){if(!await E.confirm(`A block named "${t.name}" already exists. Overwrite it with the imported version?`)){E.toast("Import cancelled.",{type:"info"});return}try{const c=await i.blocks.importBundle(a,{overwrite:!0});E.toast(`Overwrote "${c.name}".`,{type:"success"}),await l(e)}catch(c){E.toast(c.message||"Import failed.",{type:"error"})}return}E.toast(t.message||"Import failed.",{type:"error"})}}))}
@@ -1,5 +1,5 @@
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(--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}).
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=[],y=null,S=!0,M=null,L="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,...h]=c.split(":");return{value:p.trim(),label:h.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.innerHTML='<span data-icon="menu" data-icon-size="14" style="display:inline-flex;align-items:center;vertical-align:middle;"></span>',s.title="Drag to reorder",s.style.cssText="cursor:grab;opacity:.4;flex-shrink:0;display:inline-flex;align-items:center;",n.draggable=!0,s.addEventListener("mousedown",()=>{n.draggable=!0}),n.addEventListener("dragstart",i=>{M=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"),M===null||M===t)return;g=Z();const[C]=g.splice(M,1);g.splice(t,0,C),M=null,W(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.innerHTML='<span data-icon="chevron-down" data-icon-size="12" style="display:inline-flex;align-items:center;vertical-align:middle;"></span>',l.style.cssText="opacity:.5;transition:transform .2s;display:inline-flex;align-items:center;";const o=document.createElement("button");o.type="button",o.innerHTML='<span data-icon="x" data-icon-size="13" style="display:inline-flex;align-items:center;vertical-align:middle;"></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),W(document.getElementById("fields-list"))}),d.appendChild(s),d.appendChild(r),d.appendChild(u),d.appendChild(l),d.appendChild(o),Domma.icons.scan(d);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"),h=document.createElement("label");h.className="form-label",h.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(h),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"),B=document.createElement("label");B.className="form-label",B.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(B),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 z=document.createElement("label");z.style.cssText="display:flex;align-items:center;gap:.4rem;cursor:pointer;white-space:nowrap;";const F=document.createElement("input");F.id=`fb-required-${t}`,F.type="checkbox",F.checked=!!e.required,z.appendChild(F),z.appendChild(document.createTextNode("Required")),Y.appendChild(z),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 H=document.createElement("input");return H.id=`fb-fullwidth-${t}`,H.type="checkbox",H.checked=!!e.fullWidth,U.appendChild(H),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 W(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=[],y=null,S=!0;const t=window.location.hash.match(/\/collections\/edit\/([^/?#]+)/);t&&(y=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&&y!=="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(y);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,L=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"))),y==="roles"&&e.find("#storage-tab-btn").hide()}catch{E.toast("Failed to load collection.",{type:"error"}),R.navigate("/collections");return}W(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}),W(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(),h=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},B=e.find("#save-collection-btn");B.prop("disabled",!0);try{if(S){const m=await w.collections.create({title:l,slug:o,description:a,layout:h,columns:b,fields:c,api:p,storage:f,meta:T,...$?{bundled:!0}:{}});y=m.slug,L=f.adapter||"file",S=!1,E.toast("Collection created.",{type:"success"}),R.navigate(`/collections/edit/${m.slug}`)}else if((f.adapter||"file")!==L){let m=0;try{m=(await w.collections.listEntries(y,{limit:1}))?.total??0}catch{}const N=L==="file"?`file \u2192 ${f.adapter}`:`${L} \u2192 ${f.adapter||"file"}`;if(m>0&&await E.confirm(`You changed the storage adapter (${N}).
4
4
 
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()}};
5
+ Migrate ${m} existing ${m===1?"entry":"entries"} to the new storage?`)){const I=await w.collections.migrateStorage(y,f);L=f.adapter||"file",E.toast(`Migrated ${I.migrated} of ${I.total} entries.`,{type:"success"})}else await w.collections.update(y,{title:l,description:a,layout:h,columns:b,fields:c,api:p,storage:f,meta:T,...$?{bundled:!0}:{bundled:!1}}),L=f.adapter||"file",E.toast("Collection saved.",{type:"success"})}else await w.collections.update(y,{title:l,description:a,layout:h,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{B.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 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
+ 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 y=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(y=d?d[1]:null,!y){E.toast("No collection selected.",{type:"error"});return}const a=E.loader(t.get(0),{type:"dots"});try{if(g=await h.collections.get(y),!g){a.destroy(),E.toast("Collection not found.",{type:"error"}),R.navigate("/collections");return}const s=t.find("#entries-title").get(0);s&&(s.textContent=g.title+" \u2014 Entries");const e=t.find("#edit-schema-btn").get(0);e&&e.setAttribute("href",`#/collections/edit/${y}`);const r=t.find("#form-btn").get(0);r&&(new Set(["roles","user-profiles"]).has(y)?r.style.display="none":r.setAttribute("href",`#/forms/edit/${y}`))}catch{a.destroy(),E.toast("Failed to load collection schema.",{type:"error"});return}try{w=await h.actions.forCollection(y)}catch{w=[]}try{await v(t)}finally{a.destroy()}await k(t);const c=t.find("#entry-search").get(0);c&&c.addEventListener("input",()=>{const s=c.value.toLowerCase().trim(),e=s?p.filter(r=>Object.values(r.data||{}).some(n=>String(n).toLowerCase().includes(s))):p;C(e,t)}),t.find("#add-entry-btn").off("click").on("click",()=>{A(null,t)}),t.find("#export-btn").off("click").on("click",()=>{const s=E.slideover({title:"Export Entries",size:"sm",position:"right"}),e=document.createElement("div");e.style.cssText="padding:1.25rem;display:flex;flex-direction:column;gap:1rem;",["json","csv"].forEach(r=>{const n=document.createElement("button");n.className="btn btn-ghost",n.style.cssText="justify-content:flex-start;gap:.5rem;",n.textContent=r==="json"?"Export as JSON":"Export as CSV",n.addEventListener("click",()=>{fetch(`/api/collections/${y}/export?format=${r}`,{headers:{Authorization:`Bearer ${S.get("auth_token")||""}`}}).then(l=>l.blob()).then(l=>{const o=document.createElement("a");o.href=URL.createObjectURL(l),o.download=`${y}-entries.${r}`,document.body.appendChild(o),o.click(),document.body.removeChild(o),URL.revokeObjectURL(o.href),s.close()}).catch(()=>E.toast("Export failed.",{type:"error"}))}),e.appendChild(n)}),s.setContent(e),s.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(y),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(a=>a.access?.rowLevel);if(m.length===0)return;const d=p.map(a=>a.id);d.length!==0&&(await Promise.all(m.map(async a=>{try{const{allowed:c}=await h.actions.checkAccess(a.slug,d);x[a.slug]=new Set(c)}catch{x[a.slug]=null}})),C(p,t))}async function v(t){try{const m=await h.collections.listEntries(y,{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 c=[...(g?.fields||[]).slice(0,5).map(e=>({key:`data.${e.name}`,title:e.label||e.name,render:(r,n)=>{const l=n.data?.[e.name]??"",o=String(l),i=document.createElement("span");return i.title=o,i.textContent=o.length>60?o.slice(0,60)+"\u2026":o,i.outerHTML}})),{key:"meta",title:"Created",render:e=>D(e?.createdAt).format("DD MMM YYYY HH:mm")},{key:"id",title:"",render:e=>{const r=document.createElement("div");r.style.cssText="display:flex;gap:.3rem;justify-content:flex-end;flex-wrap:wrap;",w.forEach(o=>{if(x[o.slug]!==void 0&&x[o.slug]!==null&&!x[o.slug].has(e))return;const i=document.createElement("button");i.className="btn btn-sm btn-ghost js-run-action",i.dataset.actionSlug=o.slug,i.dataset.entryId=e,i.dataset.confirmMessage=o.trigger?.confirmMessage||"",i.title=o.title||o.slug;const f=document.createElement("span");f.setAttribute("data-icon",M(o.trigger?.icon||"zap")),i.appendChild(f),i.appendChild(document.createTextNode(" "+(o.trigger?.label||o.title))),r.appendChild(i)});const n=document.createElement("button");n.className="btn btn-sm btn-ghost js-edit-entry",n.dataset.id=e,n.setAttribute("data-tooltip","Edit"),n.innerHTML='<span data-icon="edit"></span>';const l=document.createElement("button");return l.className="btn btn-sm btn-danger js-delete-entry",l.dataset.id=e,l.setAttribute("data-tooltip","Delete"),l.innerHTML='<span data-icon="trash"></span>',r.appendChild(n),r.appendChild(l),r.outerHTML}}];T.create("#entries-table",{data:t,columns:c,emptyMessage:'No entries yet. Click "Add Entry" to get started.'});const s=document.querySelector("#entries-table");s&&(s.querySelectorAll(".js-run-action").forEach(e=>{e.addEventListener("click",async r=>{r.stopPropagation();const{actionSlug:n,entryId:l,confirmMessage:o}=e.dataset;if(!(o&&!await E.confirm(o))){e.disabled=!0;try{const i=await h.actions.execute(n,l);if(i.success)E.toast(`Action completed (${i.stepsCompleted} step${i.stepsCompleted!==1?"s":""}).`,{type:"success"}),await v({find:f=>({get:u=>document.querySelector(f)})});else{const f=i.results?.find(u=>!u.success);E.toast(f?.error||"Action failed.",{type:"error"})}}catch(i){E.toast(i.message||"Action failed.",{type:"error"})}finally{e.disabled=!1}}})}),s.querySelectorAll(".js-edit-entry").forEach(e=>{e.addEventListener("click",r=>{r.stopPropagation();const n=p.find(l=>l.id===e.dataset.id);n&&A(n,null)})}),s.querySelectorAll(".js-delete-entry").forEach(e=>{e.addEventListener("click",async r=>{if(r.stopPropagation(),!!await E.confirm("Delete this entry?"))try{await h.collections.deleteEntry(y,e.dataset.id),p=p.filter(l=>l.id!==e.dataset.id),E.toast("Entry deleted.",{type:"success"}),C(p,{find:l=>({get:o=>document.querySelector(l)})})}catch{E.toast("Failed to delete entry.",{type:"error"})}})}),s.querySelectorAll("tbody tr").forEach((e,r)=>{const n=t[r];n&&(e.style.cursor="pointer",e.addEventListener("click",l=>{l.target.closest(".js-edit-entry, .js-delete-entry")||q(n)}))}),Domma.icons.scan(s),s.querySelectorAll("[data-tooltip]").forEach(e=>{E.tooltip(e,{content:e.getAttribute("data-tooltip"),position:"top"})}))}function N(t,m){const d={};return t.forEach(a=>{const c={};a.fullWidth?c.span=m:a.span>1&&(c.span=a.span),a.placeholder&&(c.placeholder=a.placeholder),a.helper&&(c.hint=a.helper);const s=a.type==="checkbox"?"boolean":a.type==="date"?"string":a.type;d[a.name]={type:s,label:a.label,required:a.required,options:a.options,...Object.keys(c).length?{formConfig:c}:{}}}),d}function A(t,m){const d=!!t,a=E.modal({title:d?"Edit Entry":"Add Entry",size:"md"}),c=document.createElement("div");c.style.cssText="padding:.25rem 0 .5rem;";const s=g?.fields||[],e=g?.columns||2,r=g?.layout||"stacked",n=document.createElement("div");F.create(N(s,e),t?.data||{},{layout:r,columns:e,showSubmitButton:!1}).renderTo(n),(s||[]).forEach(f=>{if(f.type!=="date"||!f.name)return;const u=n.querySelector(`[name="${f.name}"]`);u&&u.type!=="date"&&(u.type="date")}),c.appendChild(n);const l=document.createElement("div");l.style.cssText="display:flex;justify-content:flex-end;gap:.5rem;margin-top:.5rem;";const o=document.createElement("button");o.className="btn btn-ghost",o.textContent="Cancel";const i=document.createElement("button");i.className="btn btn-primary",i.textContent=d?"Save Changes":"Add Entry",l.appendChild(o),l.appendChild(i),c.appendChild(l),a.element.appendChild(c),a.open(),o.addEventListener("click",()=>a.close()),i.addEventListener("click",async()=>{const f={};s.forEach(u=>{const b=n.querySelector(`[name="${u.name}"]`);b&&(f[u.name]=u.type==="checkbox"?b.checked:b.value)}),i.disabled=!0;try{if(d){const u=await h.collections.updateEntry(y,t.id,f),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(y,f);p.unshift(u),E.toast("Entry added.",{type:"success"})}a.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||[],a=document.createElement("div");a.style.cssText="display:flex;flex-direction:column;gap:.75rem;margin-bottom:1.25rem;",d.forEach(e=>{const r=t.data?.[e.name];if(r==null||r==="")return;const n=document.createElement("div");n.style.cssText="border-bottom:1px solid var(--dm-border,#333);padding-bottom:.6rem;";const l=document.createElement("strong");l.textContent=e.label||e.name,l.style.cssText="display:block;font-size:.8rem;color:var(--dm-text-muted,#888);margin-bottom:.2rem;";const o=document.createElement("p");o.textContent=String(r),o.style.cssText="margin:0;word-break:break-word;",n.appendChild(l),n.appendChild(o),a.appendChild(n)});const c=document.createElement("div");if(c.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")}`,c.appendChild(e)}if(t.meta?.source){const e=document.createElement("span");e.textContent=`Source: ${t.meta.source}`,c.appendChild(e)}m.appendChild(a),m.appendChild(c);const s=E.modal({title:"Entry Details",size:"md"});s.element.appendChild(m),s.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 a=document.createElement("p");a.textContent='Paste a JSON array of entries. Each item should have a "data" object with field values.',a.style.cssText="font-size:.875rem;color:var(--dm-text-muted,#888);margin:0;",d.appendChild(a);const c=document.createElement("textarea");c.className="form-input",c.rows=10,c.placeholder='[{"data": {"name": "Example"}}, ...]',d.appendChild(c);const s=document.createElement("p");s.style.cssText="font-size:.875rem;margin:0;display:none;",d.appendChild(s);const e=document.createElement("div");e.style.cssText="display:flex;justify-content:flex-end;gap:.5rem;";const r=document.createElement("button");r.className="btn btn-ghost",r.textContent="Cancel";const n=document.createElement("button");n.className="btn btn-primary",n.textContent="Import",e.appendChild(r),e.appendChild(n),d.appendChild(e),m.element.appendChild(d),m.open(),r.addEventListener("click",()=>m.close()),n.addEventListener("click",async()=>{let l;try{if(l=JSON.parse(c.value),!Array.isArray(l))throw new Error("Must be a JSON array")}catch(o){s.style.display="",s.style.color="var(--dm-danger,#f87171)",s.textContent=`Invalid JSON: ${o.message}`;return}n.disabled=!0;try{const o=await h.collections.import(y,l);s.style.display="",s.style.color="var(--dm-success,#4ade80)",s.textContent=`Imported ${o.imported} entries. Skipped: ${o.skipped}.`,await v(t||{find:()=>({get:()=>null,off:()=>({on:()=>{}})})}),setTimeout(()=>m.close(),1500)}catch(o){s.style.display="",s.style.color="var(--dm-danger,#f87171)",s.textContent=o.message||"Import failed."}finally{n.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={},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>
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 s of Object.keys(c))c[s]?.destroy?.(),delete c[s];const n=window.location.hash.match(/\/components\/edit\/([^/?#]+)/);n&&(p=decodeURIComponent(n[1]));const o=E.loader(e.get(0),{type:"dots"});try{A(e),await T(e),L(e),z(e),await D(e)}finally{o.destroy()}const t=e.find("#component-preview-iframe").get(0);t.contentDocument?.readyState==="complete"?h(e):t.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>
@@ -1,5 +1,5 @@
1
- import{api as i}from"../api.js";import{filterByProject as u,getProjectFromHash as f}from"../lib/project-context.js";function p(t){return String(t??"").replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")}function g(t){for(;t.firstChild;)t.removeChild(t.firstChild)}export const componentsView={templateUrl:"/admin/js/templates/components.html",async onMount(t){y(),await l(t),k(t),Domma.icons.scan(t.get(0))}};let d=!1;function y(){d||(d=!0,I.register("download",{viewBox:"0 0 24 24",paths:["M12 3v12","M7 10l5 5 5-5","M5 19h14"],stroke:"currentColor",fill:"none",strokeWidth:2,strokeLinecap:"round",strokeLinejoin:"round"}),I.register("upload",{viewBox:"0 0 24 24",paths:["M12 21V9","M7 14l5-5 5 5","M5 5h14"],stroke:"currentColor",fill:"none",strokeWidth:2,strokeLinecap:"round",strokeLinejoin:"round"}),I.register("plus",{viewBox:"0 0 24 24",paths:["M12 5v14","M5 12h14"],stroke:"currentColor",fill:"none",strokeWidth:2,strokeLinecap:"round",strokeLinejoin:"round"}))}async function l(t){const o=t.find("#components-table-container").get(0);if(!o)return;let r=[];try{r=await i.components.list()}catch(e){g(o);const n=document.createElement("p");n.className="text-muted",n.textContent=`Failed to load components: ${e.message}`,o.appendChild(n);return}const s=f();s&&(r=u(r,s)),T.create(o,{data:r,emptyMessage:'No components yet. Click "New Component" to create your first one.',columns:[{key:"name",title:"Name",render:(e,n)=>{const a=n.origin==="plugin"?' <span class="badge badge-secondary" title="Provided by a plugin \u2014 read-only">plugin</span>':"";return`<a href="#/components/edit/${p(e)}">${p(e)}.dmc</a>${a}`}},{key:"props",title:"Props",render:e=>{const n=Object.keys(e||{});return n.length===0?'<span class="text-muted">\u2014</span>':n.map(a=>`<span class="badge badge-secondary" style="margin-right:.25rem;">${p(a)}</span>`).join("")}},{key:"size",title:"Size",render:e=>`${e} B`},{key:"updatedAt",title:"Updated",render:e=>e?new Date(e).toLocaleString():"\u2014"},{key:"name",title:"Actions",render:(e,n)=>{const a=p(e),m=n.origin==="plugin"?"":`<button class="btn btn-sm btn-danger js-delete-component" data-name="${a}" data-tooltip="Delete"><span data-icon="trash"></span></button>`;return`<div style="display:flex;gap:.4rem;justify-content:flex-end;">
1
+ import{api as c}from"../api.js";import{filterByProject as u,getProjectFromHash as f}from"../lib/project-context.js";function l(t){return String(t??"").replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")}function g(t){for(;t.firstChild;)t.removeChild(t.firstChild)}export const componentsView={templateUrl:"/admin/js/templates/components.html",async onMount(t){y(),await d(t),k(t),Domma.icons.scan(t.get(0))}};let p=!1;function y(){p||(p=!0,I.register("download",{viewBox:"0 0 24 24",paths:["M12 3v12","M7 10l5 5 5-5","M5 19h14"],stroke:"currentColor",fill:"none",strokeWidth:2,strokeLinecap:"round",strokeLinejoin:"round"}),I.register("upload",{viewBox:"0 0 24 24",paths:["M12 21V9","M7 14l5-5 5 5","M5 5h14"],stroke:"currentColor",fill:"none",strokeWidth:2,strokeLinecap:"round",strokeLinejoin:"round"}),I.register("plus",{viewBox:"0 0 24 24",paths:["M12 5v14","M5 12h14"],stroke:"currentColor",fill:"none",strokeWidth:2,strokeLinecap:"round",strokeLinejoin:"round"}))}async function d(t){const o=t.find("#components-table-container").get(0);if(!o)return;let r=[];const i=E.loader(t.get(0),{type:"dots"});try{r=await c.components.list()}catch(e){g(o);const n=document.createElement("p");n.className="text-muted",n.textContent=`Failed to load components: ${e.message}`,o.appendChild(n);return}finally{i.destroy()}const s=f();s&&(r=u(r,s)),T.create(o,{data:r,emptyMessage:'No components yet. Click "New Component" to create your first one.',columns:[{key:"name",title:"Name",render:(e,n)=>{const a=n.origin==="plugin"?' <span class="badge badge-secondary" title="Provided by a plugin \u2014 read-only">plugin</span>':"";return`<a href="#/components/edit/${l(e)}">${l(e)}.dmc</a>${a}`}},{key:"props",title:"Props",render:e=>{const n=Object.keys(e||{});return n.length===0?'<span class="text-muted">\u2014</span>':n.map(a=>`<span class="badge badge-secondary" style="margin-right:.25rem;">${l(a)}</span>`).join("")}},{key:"size",title:"Size",render:e=>`${e} B`},{key:"updatedAt",title:"Updated",render:e=>e?new Date(e).toLocaleString():"\u2014"},{key:"name",title:"Actions",render:(e,n)=>{const a=l(e),m=n.origin==="plugin"?"":`<button class="btn btn-sm btn-danger js-delete-component" data-name="${a}" data-tooltip="Delete"><span data-icon="trash"></span></button>`;return`<div style="display:flex;gap:.4rem;justify-content:flex-end;">
2
2
  <a href="#/components/edit/${a}" class="btn btn-sm btn-ghost" data-tooltip="Edit"><span data-icon="edit"></span></a>
3
3
  <button class="btn btn-sm btn-ghost js-export-component" data-name="${a}" data-tooltip="Export as .dmcomponent.json"><span data-icon="download"></span></button>
4
4
  ${m}
5
- </div>`}}]}),o.querySelectorAll(".js-delete-component").forEach(e=>{e.addEventListener("click",async()=>{await h(e.dataset.name,t)})}),o.querySelectorAll(".js-export-component").forEach(e=>{e.addEventListener("click",async()=>{try{await i.components.exportBundle(e.dataset.name)}catch(n){E.toast(n.message||"Export failed.",{type:"error"})}})}),Domma.icons.scan(o),o.querySelectorAll("[data-tooltip]").forEach(e=>{E.tooltip(e,{content:e.getAttribute("data-tooltip"),position:"top"})})}async function h(t,o){if(await E.confirm(`Delete component "${t}"? This cannot be undone.`))try{await i.components.delete(t),E.toast("Component deleted.",{type:"success"}),await l(o)}catch(s){E.toast(s.message||"Failed to delete component.",{type:"error"})}}function k(t){const o=t.find("#import-component-btn").get(0),r=t.find("#import-component-file").get(0);!o||!r||(o.addEventListener("click",()=>r.click()),r.addEventListener("change",async()=>{const s=r.files?.[0];if(!s)return;r.value="";let e;try{const n=await s.text();e=JSON.parse(n)}catch{E.toast("Not a valid .dmcomponent.json file (could not parse JSON).",{type:"error"});return}if(!e||typeof e!="object"||!e.name||typeof e.source!="string"){E.toast("Bundle is missing a name or source field.",{type:"error"});return}try{const n=await i.components.importBundle(e);E.toast(`Imported "${n.name}".`,{type:"success"}),await l(t)}catch(n){if(n.code==="CONFLICT"){if(!await E.confirm(`A component named "${n.name}" already exists. Overwrite it with the imported version?`)){E.toast("Import cancelled.",{type:"info"});return}try{const c=await i.components.importBundle(e,{overwrite:!0});E.toast(`Overwrote "${c.name}".`,{type:"success"}),await l(t)}catch(c){E.toast(c.message||"Import failed.",{type:"error"})}return}E.toast(n.message||"Import failed.",{type:"error"})}}))}
5
+ </div>`}}]}),o.querySelectorAll(".js-delete-component").forEach(e=>{e.addEventListener("click",async()=>{await h(e.dataset.name,t)})}),o.querySelectorAll(".js-export-component").forEach(e=>{e.addEventListener("click",async()=>{try{await c.components.exportBundle(e.dataset.name)}catch(n){E.toast(n.message||"Export failed.",{type:"error"})}})}),Domma.icons.scan(o),o.querySelectorAll("[data-tooltip]").forEach(e=>{E.tooltip(e,{content:e.getAttribute("data-tooltip"),position:"top"})})}async function h(t,o){if(await E.confirm(`Delete component "${t}"? This cannot be undone.`))try{await c.components.delete(t),E.toast("Component deleted.",{type:"success"}),await d(o)}catch(i){E.toast(i.message||"Failed to delete component.",{type:"error"})}}function k(t){const o=t.find("#import-component-btn").get(0),r=t.find("#import-component-file").get(0);!o||!r||(o.addEventListener("click",()=>r.click()),r.addEventListener("change",async()=>{const i=r.files?.[0];if(!i)return;r.value="";let s;try{const e=await i.text();s=JSON.parse(e)}catch{E.toast("Not a valid .dmcomponent.json file (could not parse JSON).",{type:"error"});return}if(!s||typeof s!="object"||!s.name||typeof s.source!="string"){E.toast("Bundle is missing a name or source field.",{type:"error"});return}try{const e=await c.components.importBundle(s);E.toast(`Imported "${e.name}".`,{type:"success"}),await d(t)}catch(e){if(e.code==="CONFLICT"){if(!await E.confirm(`A component named "${e.name}" already exists. Overwrite it with the imported version?`)){E.toast("Import cancelled.",{type:"info"});return}try{const a=await c.components.importBundle(s,{overwrite:!0});E.toast(`Overwrote "${a.name}".`,{type:"success"}),await d(t)}catch(a){E.toast(a.message||"Import failed.",{type:"error"})}return}E.toast(e.message||"Import failed.",{type:"error"})}}))}
@@ -1 +1 @@
1
- import{apiRequest as a}from"/admin/js/api.js";export const effectsView={templateUrl:"/admin/js/templates/effects.html",async onMount(t){let e={};try{e=await a("/effects/settings")}catch{E.toast("Could not load settings.",{type:"error"})}t.find("#field-respect-motion").prop("checked",e.respectMotion!==!1),t.find("#field-default-duration").val(e.defaultDuration??600),t.find("#field-default-animation").val(e.defaultAnimation||"fade"),t.find("#field-default-threshold").val(e.defaultThreshold??.1),t.find("#save-settings-btn").off("click").on("click",async()=>{const s={respectMotion:t.find("#field-respect-motion").prop("checked"),defaultDuration:parseInt(t.find("#field-default-duration").val(),10)||600,defaultAnimation:t.find("#field-default-animation").val(),defaultThreshold:parseFloat(t.find("#field-default-threshold").val())||.1};try{await a("/effects/settings",{method:"PUT",body:JSON.stringify(s)}),E.toast("Settings saved.",{type:"success"})}catch{E.toast("Failed to save settings.",{type:"error"})}}),E.tabs(t.find("#effects-tabs").get(0)),t.find(".effects-tab-btn").on("click",function(){const s=this.getAttribute("data-fx-tab");t.find(".effects-tab-btn").removeClass("active"),$(this).addClass("active"),t.find(".effects-tab-panel").hide(),t.find(`#tab-${s}`).show()}),t.find(".effects-tab-panel").hide(),t.find("#tab-entrance").show(),Domma.icons.scan()}};
1
+ import{apiRequest as a}from"/admin/js/api.js";export const effectsView={templateUrl:"/admin/js/templates/effects.html",async onMount(t){let e={};const f=E.loader(t.get(0),{type:"dots"});try{e=await a("/effects/settings")}catch{E.toast("Could not load settings.",{type:"error"})}finally{f.destroy()}t.find("#field-respect-motion").prop("checked",e.respectMotion!==!1),t.find("#field-default-duration").val(e.defaultDuration??600),t.find("#field-default-animation").val(e.defaultAnimation||"fade"),t.find("#field-default-threshold").val(e.defaultThreshold??.1),t.find("#save-settings-btn").off("click").on("click",async()=>{const s={respectMotion:t.find("#field-respect-motion").prop("checked"),defaultDuration:parseInt(t.find("#field-default-duration").val(),10)||600,defaultAnimation:t.find("#field-default-animation").val(),defaultThreshold:parseFloat(t.find("#field-default-threshold").val())||.1};try{await a("/effects/settings",{method:"PUT",body:JSON.stringify(s)}),E.toast("Settings saved.",{type:"success"})}catch{E.toast("Failed to save settings.",{type:"error"})}}),E.tabs(t.find("#effects-tabs").get(0)),t.find(".effects-tab-btn").on("click",function(){const s=this.getAttribute("data-fx-tab");t.find(".effects-tab-btn").removeClass("active"),$(this).addClass("active"),t.find(".effects-tab-panel").hide(),t.find(`#tab-${s}`).show()}),t.find(".effects-tab-panel").hide(),t.find("#tab-entrance").show(),Domma.icons.scan()}};