domma-cms 0.76.0 → 0.77.0

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 (88) hide show
  1. package/CLAUDE.md +1 -1
  2. package/admin/js/api.js +1 -1
  3. package/admin/js/app.js +2 -2
  4. package/admin/js/lib/action-editor-arrange.js +1 -0
  5. package/admin/js/lib/api-endpoint-editor-arrange.js +2 -0
  6. package/admin/js/lib/block-editor-arrange.js +2 -0
  7. package/admin/js/lib/collection-editor-arrange.js +3 -0
  8. package/admin/js/lib/collection-entries-arrange.js +1 -0
  9. package/admin/js/lib/component-editor-arrange.js +27 -0
  10. package/admin/js/lib/context-menu-editor-arrange.js +1 -0
  11. package/admin/js/lib/dashboard-arrange.js +1 -0
  12. package/admin/js/lib/form-editor-arrange.js +1 -0
  13. package/admin/js/lib/form-submissions-arrange.js +2 -0
  14. package/admin/js/lib/menu-editor-arrange.js +2 -0
  15. package/admin/js/lib/page-editor-arrange.js +1 -0
  16. package/admin/js/lib/plugin-code-arrange.js +1 -0
  17. package/admin/js/lib/project-page-arrange.js +1 -0
  18. package/admin/js/lib/role-editor-arrange.js +3 -0
  19. package/admin/js/lib/user-editor-arrange.js +1 -0
  20. package/admin/js/lib/view-editor-arrange.js +14 -0
  21. package/admin/js/templates/action-editor.html +223 -147
  22. package/admin/js/templates/api-endpoint-editor.html +172 -117
  23. package/admin/js/templates/block-editor.html +194 -203
  24. package/admin/js/templates/collection-editor.html +191 -113
  25. package/admin/js/templates/collection-entries.html +99 -36
  26. package/admin/js/templates/component-editor.html +128 -126
  27. package/admin/js/templates/context-menu-editor.html +208 -148
  28. package/admin/js/templates/dashboard.html +184 -22
  29. package/admin/js/templates/form-editor.html +41 -26
  30. package/admin/js/templates/form-submissions.html +78 -26
  31. package/admin/js/templates/menu-editor.html +57 -17
  32. package/admin/js/templates/menu-locations.html +54 -11
  33. package/admin/js/templates/page-editor.html +47 -26
  34. package/admin/js/templates/plugin-code.html +91 -73
  35. package/admin/js/templates/project-detail.html +83 -145
  36. package/admin/js/templates/project-settings.html +88 -53
  37. package/admin/js/templates/role-editor.html +158 -83
  38. package/admin/js/templates/user-editor.html +150 -14
  39. package/admin/js/templates/view-editor.html +203 -180
  40. package/admin/js/templates/view-preview.html +45 -45
  41. package/admin/js/views/action-editor.js +7 -1
  42. package/admin/js/views/api-endpoint-editor.js +2 -3
  43. package/admin/js/views/block-editor.js +14 -8
  44. package/admin/js/views/collection-editor.js +64 -5
  45. package/admin/js/views/collection-entries.js +50 -1
  46. package/admin/js/views/component-editor.js +2 -28
  47. package/admin/js/views/context-menu-editor.js +54 -55
  48. package/admin/js/views/dashboard.js +7 -1
  49. package/admin/js/views/form-editor.js +7 -7
  50. package/admin/js/views/form-submissions.js +11 -1
  51. package/admin/js/views/index.js +1 -1
  52. package/admin/js/views/login.js +2 -2
  53. package/admin/js/views/menu-editor.js +21 -43
  54. package/admin/js/views/menu-locations.js +16 -1
  55. package/admin/js/views/page-editor.js +26 -26
  56. package/admin/js/views/plugin-code.js +23 -4
  57. package/admin/js/views/project-detail.js +9 -1
  58. package/admin/js/views/project-settings.js +1 -1
  59. package/admin/js/views/role-editor.js +11 -3
  60. package/admin/js/views/user-editor.js +1 -1
  61. package/admin/js/views/view-editor.js +1 -1
  62. package/admin/js/views/view-preview.js +1 -1
  63. package/package.json +1 -1
  64. package/server/routes/api/dashboard.js +32 -4
  65. package/server/routes/api/forms.js +104 -13
  66. package/server/routes/api/users.js +12 -2
  67. package/server/routes/api/views.js +55 -1
  68. package/server/services/components.js +9 -0
  69. package/server/services/users.js +2 -2
  70. package/server/services/views.js +14 -0
  71. package/admin/js/templates/dashboard/activity-feed.html +0 -3
  72. package/admin/js/templates/dashboard/cache.html +0 -32
  73. package/admin/js/templates/dashboard/health-detail.html +0 -2
  74. package/admin/js/templates/dashboard/journeys.html +0 -17
  75. package/admin/js/templates/dashboard/kpi-strip.html +0 -34
  76. package/admin/js/templates/dashboard/spike-feed.html +0 -3
  77. package/admin/js/templates/dashboard/top-pages.html +0 -3
  78. package/admin/js/templates/dashboard/traffic-chart.html +0 -3
  79. package/admin/js/views/block-editor-enhance.js +0 -1
  80. package/admin/js/views/dashboard/lib/escape.js +0 -1
  81. package/admin/js/views/dashboard/widgets/activity-feed.js +0 -1
  82. package/admin/js/views/dashboard/widgets/cache.js +0 -1
  83. package/admin/js/views/dashboard/widgets/health-detail.js +0 -1
  84. package/admin/js/views/dashboard/widgets/journeys.js +0 -1
  85. package/admin/js/views/dashboard/widgets/kpi-strip.js +0 -1
  86. package/admin/js/views/dashboard/widgets/spike-feed.js +0 -6
  87. package/admin/js/views/dashboard/widgets/top-pages.js +0 -1
  88. package/admin/js/views/dashboard/widgets/traffic-chart.js +0 -1
@@ -1 +1,7 @@
1
- import{api as _}from"../api.js";let N=null,L={};async function J(e){const t=e.find("#action-project").get(0);if(t)try{(await _.projects.list()).forEach(l=>{const n=document.createElement("option");n.value=l.slug,n.textContent=l.name||l.slug,t.appendChild(n)})}catch{}}const z={deleteEntry:[],moveToCollection:[{name:"targetCollection",label:"Target Collection Slug",placeholder:"e.g. archived-applications"}],webhook:[{name:"url",label:"URL",placeholder:"https://hooks.example.com/notify"},{name:"method",label:"Method",placeholder:"POST"},{name:"body",label:"Body (JSON)",placeholder:'{"email": "{{entry.data.email}}"}',multiline:!0}],email:[{name:"to",label:"To",placeholder:"{{entry.data.email}}"},{name:"subject",label:"Subject",placeholder:"Your application update"},{name:"template",label:"Body",placeholder:"Your application has been approved.",multiline:!0}]};export const actionEditorView={templateUrl:"/admin/js/templates/action-editor.html",async onMount(e){N=null,L={};const t=window.location.hash.match(/\/actions\/edit\/([^/?#]+)/);t&&(N=t[1]),E.tabs(e.find("#action-editor-tabs").get(0)),await P(e),await U(e),await J(e),N&&(e.find("#action-editor-title").text("Edit Action"),await B(e,N)),e.find("#add-step-btn").off("click").on("click",()=>{const a=e.find("#add-step-type").val()||"updateField";M(e,{type:a,config:{}})}),e.find("#save-action-btn").off("click").on("click",async()=>{await Q(e)}),K(e),Domma.icons.scan()}};async function P(e){const t=e.find("#action-collection").get(0);if(t)try{(await _.collections.list()).forEach(a=>{const l=document.createElement("option");l.value=a.slug,l.textContent=`${a.title} (${a.slug})`,t.appendChild(l)})}catch{}}async function U(e){const t=e.find("#action-roles-checkboxes").get(0);t&&["admin","manager","editor","subscriber"].forEach(a=>{const l=document.createElement("label");l.style.cssText="display:flex;align-items:center;gap:.5rem;cursor:pointer;";const n=document.createElement("input");n.type="checkbox",n.value=a,n.dataset.role=a,n.className="action-role-cb",n.checked=a==="admin",l.appendChild(n),l.appendChild(document.createTextNode(a)),t.appendChild(l)})}async function B(e,t){try{const a=await _.actions.get(t);if(!a){E.toast("Action not found.",{type:"error"}),R.navigate("/actions");return}Y(e,a)}catch(a){E.toast(a.message||"Failed to load action.",{type:"error"}),R.navigate("/actions")}}function K(e){const t=e.find("#action-rowlevel-enabled").get(0),a=e.find("#action-rowlevel-config").get(0),l=e.find("#action-rowlevel-mode").get(0),n=e.find("#action-rowlevel-field-group").get(0);t&&(t.addEventListener("change",()=>{a&&(a.style.display=t.checked?"flex":"none")}),l&&l.addEventListener("change",()=>{n&&(n.style.display=l.value==="field"?"":"none")}))}function Y(e,t){e.find("#action-title").val(t.title||""),e.find("#action-slug").val(t.slug||""),e.find("#action-description").val(t.description||""),e.find("#action-collection").val(t.collection||""),L=t.meta||{},e.find("#action-project").val(t.meta?.project||""),e.find("#action-bundled").prop("checked",!!t.bundled),e.find("#action-trigger-type").val(t.trigger?.type||"manual"),e.find("#action-trigger-label").val(t.trigger?.label||"Run"),e.find("#action-trigger-icon").val(t.trigger?.icon||"zap"),e.find("#action-trigger-confirm").val(t.trigger?.confirmMessage||"");const a=t.access?.roles||["admin"];e.find(".action-role-cb").each(function(){this.checked=a.includes(this.value)});const l=t.access?.rowLevel;l&&(e.find("#action-rowlevel-enabled").prop("checked",!0),e.find("#action-rowlevel-config").css("display","flex"),e.find("#action-rowlevel-mode").val(l.mode||"owner"),e.find("#action-rowlevel-userkey").val(l.userKey||"id"),l.mode==="field"&&(e.find("#action-rowlevel-field-group").css("display",""),e.find("#action-rowlevel-field").val(l.field||"")));const n=e.find("#action-steps-list").get(0);if(n){const s=n.querySelector(".steps-empty-placeholder");for(s&&s.remove();n.firstChild;)n.removeChild(n.firstChild);const i=document.createElement("p");i.className="text-muted steps-empty-placeholder",i.textContent="No steps yet. Add a step to define what this action does.",i.style.cssText="text-align:center;padding:2rem 0;",n.appendChild(i)}(t.steps||[]).forEach(s=>M(e,s))}function M(e,t){const a=e.find("#action-steps-list").get(0);if(!a)return;const l=a.querySelector(".steps-empty-placeholder");l&&l.remove();const n=z[t.type]||[],s=document.createElement("div");s.className="card mb-2 step-card",s.dataset.stepType=t.type;const i=document.createElement("div");i.className="card-header",i.style.cssText="display:flex;align-items:center;gap:.5rem;";const p=document.createElement("code");p.textContent=t.type,p.style.cssText="flex:1;font-size:.85rem;";const r=document.createElement("button");r.type="button",r.className="btn btn-sm btn-ghost";const f=document.createElement("span");f.setAttribute("data-icon","trash"),r.appendChild(f),r.addEventListener("click",()=>{if(s.remove(),!a.querySelector(".step-card")){const o=document.createElement("p");o.className="text-muted steps-empty-placeholder",o.textContent="No steps yet. Add a step to define what this action does.",o.style.cssText="text-align:center;padding:2rem 0;",a.appendChild(o)}}),i.appendChild(p),i.appendChild(r);const h=document.createElement("div");if(h.className="card-body",n.length===0){const o=document.createElement("p");o.className="text-muted",o.textContent=t.type==="deleteEntry"?"This step deletes the entry. No configuration required.":"No additional configuration required.",o.style.margin="0",h.appendChild(o)}t.type==="updateField"?G(h,t,e):n.forEach(o=>{const g=document.createElement("div");g.style.cssText="margin-bottom:.75rem;";const b=document.createElement("label");b.className="form-label",b.textContent=o.label,g.appendChild(b);let m;o.multiline?(m=document.createElement("textarea"),m.rows=3,m.style.cssText="font-family:monospace;font-size:.8rem;resize:vertical;",m.value=typeof t.config?.[o.name]=="object"?JSON.stringify(t.config[o.name],null,2):t.config?.[o.name]??""):(m=document.createElement("input"),m.type="text",m.value=t.config?.[o.name]??""),m.className=`form-input step-field-${o.name}`,m.placeholder=o.placeholder||"",m.dataset.field=o.name,g.appendChild(m),h.appendChild(g)}),s.appendChild(i),s.appendChild(h),a.appendChild(s),Domma.icons.scan(s)}async function G(e,t,a){const l=a.find("#action-collection").val(),n=document.createElement("div");n.style.cssText="margin-bottom:.75rem;";const s=document.createElement("label");s.className="form-label",s.textContent="Field",n.appendChild(s);const i=document.createElement("select");i.className="form-input step-field-field",i.dataset.field="field";const p=document.createElement("input");p.type="text",p.className="form-input mt-2 step-field-field-custom",p.placeholder="Field name, e.g. status",p.style.display="none";const r=document.createElement("div");r.style.cssText="margin-bottom:.75rem;";const f=document.createElement("label");f.className="form-label",f.textContent="New Value",r.appendChild(f);const h=document.createElement("div");r.appendChild(h);const o=document.createElement("button");o.type="button",o.className="btn btn-ghost btn-sm mt-2",o.style.cssText="font-size:.75rem;padding:.2rem .5rem;",o.textContent="Template Variables";const g=document.createElement("div");g.style.cssText="display:none;background:var(--dm-surface-raised,#1e1e2e);border-radius:.4rem;padding:.75rem;margin-top:.5rem;font-size:.8rem;line-height:1.7;";const b=document.createElement("strong");b.textContent="Available template variables:",g.appendChild(b);const m=document.createElement("table");m.style.cssText="width:100%;border-collapse:collapse;margin-top:.4rem;",[["{{now}}","Current ISO timestamp"],["{{user.id}}","Executing user's ID"],["{{user.name}}","Executing user's name"],["{{user.email}}","Executing user's email"],["{{entry.id}}","Entry's unique ID"],["{{entry.data.fieldName}}","Any field value from the entry"],["{{env.CMS_PUBLIC_*}}","Public environment variables"]].forEach(([d,u])=>{const x=document.createElement("tr"),w=document.createElement("td");w.style.cssText="padding:.2rem .5rem .2rem 0;opacity:.7;white-space:nowrap;";const T=document.createElement("code");T.textContent=d,w.appendChild(T);const c=document.createElement("td");c.textContent=u,x.appendChild(w),x.appendChild(c),m.appendChild(x)}),g.appendChild(m),o.addEventListener("click",()=>{const d=g.style.display!=="none";g.style.display=d?"none":"",o.textContent=d?"Template Variables":"Hide Variables"}),r.appendChild(o),r.appendChild(g),e.appendChild(n),e.appendChild(r);let S=[];if(l)try{S=(await _.collections.get(l)).fields||[]}catch{}const F=document.createElement("option");F.value="",F.textContent=S.length?"- select a field -":"- no fields available -",i.appendChild(F),S.forEach(d=>{const u=document.createElement("option");u.value=d.name,u.textContent=`${d.label} (${d.name})`,u.dataset.fieldType=d.type,u.dataset.fieldOptions=d.type==="select"?JSON.stringify(d.options||[]):"",i.appendChild(u)});const j=document.createElement("option");j.value="__custom__",j.textContent="- enter manually -",i.appendChild(j);const C=t.config?.field||"",V=C&&[...i.options].find(d=>d.value===C);V?i.value=C:C&&(i.value="__custom__",p.value=C,p.style.display=""),n.appendChild(i),n.appendChild(p);function I(d,u){h.textContent="";const x=d?[...i.options].find(c=>c.value===d):null,w=x?.dataset.fieldType==="select",T=w?JSON.parse(x.dataset.fieldOptions||"[]"):[];if(w&&T.length){const c=document.createElement("select");c.className="form-input step-field-value",c.dataset.field="value";const O=document.createElement("option");O.value="",O.textContent="- select a value -",c.appendChild(O),T.forEach(y=>{const k=typeof y=="string"?y:y.value??"",D=typeof y=="string"?y:y.label||y.value||k;if(!k||k==="undefined")return;const q=document.createElement("option");q.value=k,q.textContent=D,c.appendChild(q)});const A=document.createElement("option");A.value="__custom__",A.textContent="- enter manually -",c.appendChild(A);const v=document.createElement("input");v.type="text",v.className="form-input mt-2",v.placeholder="e.g. approved or {{now}}",v.style.display="none",u&&[...c.options].find(y=>y.value===u&&y.value!=="__custom__")?c.value=u:u&&(c.value="__custom__",v.value=u,v.style.display=""),c.addEventListener("change",()=>{const y=c.value==="__custom__";v.style.display=y?"":"none",y||(v.value="")}),h.appendChild(c),h.appendChild(v)}else{const c=document.createElement("input");c.type="text",c.className="form-input step-field-value",c.dataset.field="value",c.placeholder="e.g. approved or {{now}}",c.value=u||"",h.appendChild(c)}}I(V?C:null,t.config?.value||""),i.addEventListener("change",()=>{const d=i.value==="__custom__";p.style.display=d?"":"none",d||(p.value=""),I(d?null:i.value,"")})}function H(e){const t=[];return e.find(".step-card").each(function(){const a=this.dataset.stepType,l={};if(a==="updateField"){let n=this.querySelector(".step-field-field")?.value?.trim()||"";n==="__custom__"&&(n=this.querySelector(".step-field-field-custom")?.value?.trim()||""),l.field=n;const s=this.querySelector(".step-field-value");let i=s?.value?.trim()||"";i==="__custom__"&&(i=s?.nextElementSibling?.value?.trim()||""),l.value=i}else(z[a]||[]).forEach(n=>{const s=this.querySelector(`.step-field-${n.name}`);if(!s)return;const i=s.value.trim();if(n.multiline&&i)try{l[n.name]=JSON.parse(i)}catch{l[n.name]=i}else l[n.name]=i});t.push({type:a,config:l})}),t}async function Q(e){const t=e.find("#action-title").val().trim();if(!t){E.toast("Title is required.",{type:"warning"});return}const a=e.find("#action-collection").val();if(!a){E.toast("Target collection is required (General tab).",{type:"warning"});return}const l=[];e.find(".action-role-cb:checked").each(function(){l.push(this.value)});const n=e.find("#action-rowlevel-enabled").is(":checked");let s=null;if(n){const f=e.find("#action-rowlevel-mode").val()||"owner",h=e.find("#action-rowlevel-userkey").val()||"id";if(s={mode:f,userKey:h},f==="field"){const o=e.find("#action-rowlevel-field").val().trim();if(!o){E.toast("Field name is required for Field Match mode.",{type:"warning"});return}s.field=o}}const i=e.find("#action-project").val()||"",p={title:t,slug:e.find("#action-slug").val().trim()||void 0,description:e.find("#action-description").val().trim(),collection:a,...e.find("#action-bundled").is(":checked")?{bundled:!0}:{},trigger:{type:e.find("#action-trigger-type").val()||"manual",label:e.find("#action-trigger-label").val().trim()||"Run",icon:e.find("#action-trigger-icon").val().trim()||"zap",confirmMessage:e.find("#action-trigger-confirm").val().trim()||null},steps:H(e),access:{roles:l,rowLevel:s},meta:{...L||{},project:i||null}},r=e.find("#save-action-btn").get(0);r&&(r.disabled=!0);try{if(N)await _.actions.update(N,p),E.toast("Action updated.",{type:"success"});else{const f=await _.actions.create(p);E.toast("Action created.",{type:"success"}),R.navigate(`/actions/edit/${f.slug}`)}}catch(f){E.toast(f.message||"Failed to save action.",{type:"error"})}finally{r&&(r.disabled=!1)}}
1
+ import{apiRequest as L}from"../api.js";import{addBannerAction as ge}from"../lib/plugin-chrome.js";import{helpPopovers as Ke}from"../lib/help-popover.js";import{openMenuAt as Ve,sidebarCount as Se}from"../lib/tool-kit.js";import{isMongoMissing as We}from"../lib/actions-arrange.js";import{STEP_TYPES as Ye,checkAction as gt,draftKey as St,draftOf as Ee,duplicateStep as Et,formFromAction as X,makeStep as xt,moveStep as ze,payloadFromForm as kt,problemsText as Je,removeStep as wt,restoreDraft as Ct,rolePicker as Mt,slugify as Ge,snapshot as N,stepSummary as Qe}from"../lib/action-editor-arrange.js";const _t=/^\/actions\/(new|edit\/[^/?#]+)\/?(\?.*)?$/,re=h=>String(h||"").split("?")[0].replace(/\/$/,""),xe="#/actions",$t=`
2
+ <label class="aex-role" data-role="{{name}}">
3
+ <input type="checkbox" class="aex-role-cb" data-bind-checked="on">
4
+ <span class="aex-step-main"><strong>{{label}}</strong><small>{{name}}</small></span>
5
+ <span class="aex-chip" data-bind-hidden="!levelText">{{levelText}}</span>
6
+ <span class="aex-chip is-warn" data-bind-hidden="!missing" title="The action names it, but System \u203A Roles has no such role">No longer exists</span>
7
+ </label>`,Lt=[[["entry.data.fieldName"],"Any field of the entry"],[["entry.id"],"The entry's id"],[["now"],"The current time (ISO)"],[["user.id","user.name","user.email"],"Who ran it"],[["env.CMS_PUBLIC_*"],"Public environment variables"]],Nt=h=>String(h??"").replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;");function ke(h,y){const l=h.querySelector(".dm-plugin-banner-name");if(!l)return;const T=l.querySelector(".dm-plugin-banner-sep");if(T){for(;T.nextSibling;)T.nextSibling.remove();T.after(document.createTextNode(y))}}let A=null,Xe=!1;function At(){Xe||!window.R?.use||(Xe=!0,R.use((h,y,l)=>{if(!(_t.test(y?.path||"")&&re(h?.path)!==re(y?.path))||!A?.dirty())return l();const m=`#${y.path}`;return E.confirm("This action has unsaved changes. Leave anyway? They are kept as a draft for your next visit.").then(Z=>{Z?l():history.replaceState(null,"",m)})}),M.subscribe("router:afterChange",({to:h}={})=>{if(A&&re(h?.path)!==A.key){const y=A;A=null,y.dispose()}}))}export const actionEditorView={templateUrl:"/admin/js/templates/action-editor.html",async onMount(h){if(At(),A){const e=A;A=null,e.dispose()}const y=h.get(0),l=y.querySelector(".aex")||y,T=[],m=e=>(T.push(e),e);let Z=!1;const C=()=>!Z&&l.isConnected,ie=location.hash.replace(/^#/,""),we=ie.match(/^\/actions\/edit\/([^/?#]+)/),x=we?decodeURIComponent(we[1]):null,_=!!x,ue=new URLSearchParams(ie.split("?")[1]||"").get("project")||"",Ze=re(ie),w=St(x),F=M.observable(!0),H=M.observable(!1),ee=M.observable(!1),j=M.observable(!1),K=X(null,{project:ue}),i={};for(const[e,t]of Object.entries(K))e!=="steps"&&e!=="roles"&&(i[e]=M.observable(t));const U=M.observable([...K.roles]);let v=K.steps;const te=M.observable(0),de=()=>{te.value=te.peek()+1},Ce=M.observable("updateField");let ne={},p=null;const Me=()=>p?X(p):K,_e=()=>{te.value;const e={steps:v,roles:U.value};for(const t of Object.keys(i))e[t]=i[t].value;return e},O=()=>{const e={steps:v,roles:U.peek()};for(const t of Object.keys(i))e[t]=i[t].peek();return e},V=M.observable(N(K)),oe=M.computed(()=>!F.value&&!H.value&&N(_e())!==V.value),ae=M.computed(()=>gt(_e())),et=M.computed(()=>{const e=ae.value;if(j.value)return e;const t={};return i.title.value&&e.title&&(t.title=e.title),e.slug&&(t.slug=e.slug),e.roles&&(t.roles=e.roles),t}),tt=M.computed(()=>j.value?Je(ae.value):""),nt=M.computed(()=>(te.value,v.length)),b=l.querySelector("#action-steps-list"),pe=M.computed(()=>ae.value.steps||{});let me=null,$e=!1;const P=l.querySelector("#action-collection"),z=l.querySelector("#action-project"),se=l.querySelector("#action-transition-field"),ot=l.querySelector("#action-collection-fields"),at=l.querySelector("#action-transition-values"),f=(e,t)=>{const n=document.createElement("option");return n.value=e,n.textContent=t,n},Le=M.observable([]),Ne=M.observable([]);m(M.effect(()=>{Ne.value=Mt(Le.value,U.value)}));const fe=new Map,J=M.observable([]);let Ae=0;async function st(e){const t=++Ae;if(!e){J.value=[];return}fe.has(e)||fe.set(e,L(`/collections/${encodeURIComponent(e)}`).then(o=>o?.fields||[]).catch(()=>[]));const n=await fe.get(e);t===Ae&&C()&&(J.value=n)}m(M.effect(()=>{st(i.collection.value)})),m(M.effect(()=>{const e=J.value,t=i.transitionField.peek();se.replaceChildren(f("",e.length?"- choose a field -":"- no fields -"));for(const n of e)se.appendChild(f(n.name,`${n.label||n.name} (${n.name})`));t&&!e.some(n=>n.name===t)&&se.appendChild(f(t,`${t} (not in the collection)`)),se.value=t,ot.replaceChildren(...e.map(n=>f(n.name,n.label||n.name))),$e&&he()}));const ve=M.computed(()=>{const e=J.value.find(t=>t.name===i.transitionField.value);return e?.type==="select"?(e.options||[]).map(t=>typeof t=="string"?t:t?.value).filter(Boolean):[]});m(M.effect(()=>{at.replaceChildren(...ve.value.map(e=>f(e,e)))}));function lt(e,t,n){const o=document.createElement("div"),r=document.createElement("label");r.className="form-label",r.textContent=t.label,o.appendChild(r);const a=document.createElement(t.multiline?"textarea":"input");return t.multiline?a.rows=3:a.type="text",a.className=`form-input step-field-${t.name}`,a.placeholder=t.placeholder||"",a.value=e.config[t.name]??"",a.addEventListener("input",()=>{e.config[t.name]=a.value,n()}),o.appendChild(a),o}function ct(e,t,n){const o=J.peek(),r=document.createElement("div"),a=document.createElement("label");a.className="form-label",a.textContent="Field",r.appendChild(a);const s=document.createElement("select");s.className="form-input step-field-field",s.appendChild(f("",o.length?"- choose a field -":"- no fields - enter one below -"));for(const g of o)s.appendChild(f(g.name,`${g.label||g.name} (${g.name})`));s.appendChild(f("__custom__","- enter a field name -"));const d=document.createElement("input");d.type="text",d.className="form-input step-field-field-custom",d.placeholder="Field name, e.g. status",o.some(g=>g.name===e.config.field)?s.value=e.config.field:e.config.field&&(s.value="__custom__",d.value=e.config.field),d.hidden=s.value!=="__custom__",r.append(s,d);const q=document.createElement("div"),W=document.createElement("label");W.className="form-label",W.textContent="New value",q.appendChild(W);const k=document.createElement("div");q.appendChild(k);const $=()=>{k.replaceChildren();const g=o.find(c=>c.name===e.config.field),Y=g?.type==="select"?(g.options||[]).map(c=>typeof c=="string"?{value:c,label:c}:{value:c?.value,label:c?.label||c?.value}).filter(c=>c.value):[],u=document.createElement("input");if(u.type="text",u.className="form-input step-field-value",u.placeholder="e.g. approved or {{now}}",Y.length){const c=document.createElement("select");c.className="form-input",c.appendChild(f("","- choose a value -"));for(const ce of Y)c.appendChild(f(ce.value,ce.label));c.appendChild(f("__custom__","- enter a value -"));const He=Y.some(ce=>ce.value===e.config.value);c.value=He?e.config.value:e.config.value?"__custom__":"",u.value=He?"":e.config.value,u.hidden=c.value!=="__custom__",c.addEventListener("change",()=>{u.hidden=c.value!=="__custom__",e.config.value=c.value==="__custom__"?u.value:c.value,n()}),u.addEventListener("input",()=>{e.config.value=u.value,n()}),k.append(c,u)}else u.value=e.config.value,u.addEventListener("input",()=>{e.config.value=u.value,n()}),k.append(u)};s.addEventListener("change",()=>{d.hidden=s.value!=="__custom__",e.config.field=s.value==="__custom__"?d.value.trim():s.value,e.config.value="",$(),n()}),d.addEventListener("input",()=>{e.config.field=d.value.trim(),n()}),$(),t.append(r,q)}function he(){if(!b)return;const e=pe.peek();b.replaceChildren(),v.forEach((t,n)=>{const o=Ye[t.type],r=document.createElement("div");r.className=`aex-step${j.peek()&&e[n]?" is-bad":""}`,r.dataset.index=String(n);const a=document.createElement("div");a.className="aex-step-head",a.tabIndex=0;const s=document.createElement("span");s.className="aex-step-n",s.textContent=String(n+1);const d=document.createElement("span");d.className="aex-step-main";const be=document.createElement("strong");be.textContent=o?.label||t.type;const q=document.createElement("small");q.className="aex-step-summary",q.textContent=Qe(t),d.append(be,q);const W=document.createElement("span");W.setAttribute("data-help",o?.help||"A step type this editor does not know - it is kept exactly as stored.");const k=document.createElement("button");k.type="button",k.className="aex-kebab",k.textContent="\u22EE",k.title="Actions",k.setAttribute("aria-label",`Actions for step ${n+1}`),a.append(s,d,W,k);const $=document.createElement("div");$.className="aex-step-body";const g=document.createElement("small");g.className="aex-err";const Y=()=>{q.textContent=Qe(t),de()};if(t.type==="updateField")ct(t,$,Y);else if(o&&o.fields.length)o.fields.forEach(u=>$.appendChild(lt(t,u,Y)));else{const u=document.createElement("p");u.className="aex-note",u.textContent=t.type==="deleteEntry"?"Deletes the entry. Nothing to set.":"Nothing to set.",$.appendChild(u)}$.appendChild(g),r.append(a,$),b.appendChild(r)}),I.scan(b),me?.(),me=Ke(b,{position:"bottom"}),Re()}m(()=>me?.());function Re(){const e=pe.peek();for(const t of b?.querySelectorAll(".aex-step")||[]){const n=Number(t.dataset.index),o=j.peek()&&e[n]||"";t.classList.toggle("is-bad",!!o);const r=t.querySelector(":scope > .aex-step-body > .aex-err");r&&(r.textContent=o,r.hidden=!o)}}m(M.effect(()=>{pe.value,j.value,Re()}));const G=e=>{v=e,he(),de()};function rt(e){const t=v[e];return t?[{header:`Step ${e+1} - ${Ye[t.type]?.label||t.type}`},{label:"Move up",icon:"arrow-up",disabled:e===0,action:()=>{G(ze(v,e,-1)),Q(e-1)}},{label:"Move down",icon:"arrow-down",disabled:e===v.length-1,action:()=>{G(ze(v,e,1)),Q(e+1)}},{label:"Duplicate",icon:"copy",action:()=>{G(Et(v,e)),Q(e+1)}},{type:"divider"},{label:"Remove",icon:"trash",danger:!0,action:()=>{G(wt(v,e)),Q(Math.min(e,v.length-1))}}]:[]}const Q=e=>requestAnimationFrame(()=>b?.querySelector(`.aex-step[data-index="${e}"] .aex-step-head`)?.focus()),it=E.contextMenu(b,{cursor:"glyph",match:".aex-step",inherit:!1,density:"compact",items:e=>rt(Number(e.dataset.index))});m(()=>it?.destroy?.());const Te=e=>{const t=e.target.closest(".aex-kebab");t&&Ve(t)},je=e=>{const t=e.target.closest(".aex-step-head");!t||e.target!==t||(e.key==="F10"&&e.shiftKey||e.key==="ContextMenu"?(e.preventDefault(),Ve(t)):e.key==="Enter"&&(e.preventDefault(),t.parentElement.querySelector(".aex-step-body input, .aex-step-body select, .aex-step-body textarea")?.focus()))};b.addEventListener("click",Te),b.addEventListener("keydown",je),m(()=>{b.removeEventListener("click",Te),b.removeEventListener("keydown",je)});function le(e){$e=!0;for(const[t,n]of Object.entries(e))t!=="steps"&&(t==="roles"?U.value=[...n]:i[t]&&(i[t].value=n));v=e.steps.map(t=>({type:t.type,config:{...t.config}})),he(),de()}async function ye(){if(ee.peek()||F.peek()||H.peek())return;j.value=!0;const e=ae.peek();if(Object.keys(e).length){E.toast(Je(e),{type:"warning",duration:6e3});return}ee.value=!0;const t=kt(O(),{meta:ne,editing:_});try{if(_){const n=await L(`/actions/${encodeURIComponent(x)}`,{method:"PUT",body:JSON.stringify(t)});if(!C())return;p=n||{...p,...t},ne=p.meta||ne,V.value=N(O()),j.value=!1;try{S.remove?S.remove(w):S.set(w,null)}catch{}ke(y,p.title||x),Se(xe),E.toast("Action saved.",{type:"success"})}else{const n=await L("/actions",{method:"POST",body:JSON.stringify(t)});V.value=N(O());try{S.remove?S.remove(w):S.set(w,null)}catch{}Se(xe),E.toast(`Action "${n.title||t.title}" created.`,{type:"success"}),C()&&(location.hash=`#/actions/edit/${encodeURIComponent(n.slug)}`)}}catch(n){if(We(n)){H.value=!0;return}E.toast(`Not saved: ${n.message||n}`,{type:"error"})}finally{ee.value=!1}}async function ut(){if(!_)return;const e=p?.title||x;if(await E.confirm(`Delete the action "${Nt(e)}"? Its button disappears from every entry. This cannot be undone.`))try{await L(`/actions/${encodeURIComponent(x)}`,{method:"DELETE"}),V.value=N(O());try{S.remove?S.remove(w):S.set(w,null)}catch{}Se(xe),E.toast(`Deleted "${e}".`,{type:"success"}),location.hash="#/actions"}catch(t){E.toast(`Not deleted: ${t.message||t}`,{type:"error"})}}function dt(){E.confirm("Discard the changes you have not saved?").then(e=>{!e||!C()||(le(X(p,{project:ue})),j.value=!1)})}l.querySelector(".aex-roles").innerHTML=$t;const pt={...i,roles:U,loading:F,needsMongo:H,saving:ee,dirty:oe,err:et,problems:tt,stepCount:nt,addType:Ce,roleRows:Ne,editing:_,dirtyText:M.computed(()=>"Unsaved changes - Save (Ctrl+S) to keep them. Leave and they are kept as a draft for next time."),slugHint:M.computed(()=>_?"":Ge(i.title.value)?`Made from the title: ${Ge(i.title.value)}`:"Made from the title"),buttonText:M.computed(()=>String(i.triggerLabel.value||"").trim()||"Run"),stateValuesText:M.computed(()=>ve.value.length?`This field's values: ${ve.value.join(", ")}`:"")},mt=E.tabs(l.querySelector(".aex-tabs"),{tabSelector:".tab-item",panelSelector:".tab-panel"}),ft=M.applyBindings(pt,l,{methods:{save:()=>ye(),discard:()=>dt(),addStep:()=>{G([...v,xt(Ce.peek())]),Q(v.length-1)}}});m(()=>ft.dispose()),m(Ke(l,{position:"bottom"}));const vt=l.querySelector(".aex-vars");for(const[e,t]of Lt){const n=document.createElement("tr"),o=document.createElement("td");e.forEach((a,s)=>{s&&o.append(", ");const d=document.createElement("code");d.textContent=`{{${a}}}`,o.appendChild(d)});const r=document.createElement("td");r.textContent=t,n.append(o,r),vt.appendChild(n)}const qe=l.querySelector(".aex-roles"),Ie=e=>{if(!e.target.matches(".aex-role-cb"))return;const t=e.target.closest("[data-role]")?.dataset.role;if(!t)return;const n=U.peek();U.value=e.target.checked?[...new Set([...n,t])]:n.filter(o=>o!==t)};qe.addEventListener("change",Ie),m(()=>qe.removeEventListener("change",Ie));const De=l.querySelector(".aex-preview-icon");m(M.effect(()=>{const e=String(i.triggerIcon.value||"").trim()||"zap",t=document.createElement("span");t.setAttribute("data-icon",e),t.setAttribute("data-icon-size","14"),De.replaceChildren(t),I.scan(De)})),ge(h,{id:"action-editor-back",icon:"arrow-left",title:"All actions",onClick:()=>{location.hash="#/actions"}});const ht=()=>{_&&ge(h,{id:"action-editor-delete",icon:"trash",title:"Delete this action",onClick:()=>ut()}),ge(h,{id:"action-editor-save",icon:"save",title:"Save (Ctrl+S)",onClick:()=>ye()})};ke(y,_?x:"New action");const Fe=e=>{!(e.ctrlKey||e.metaKey)||e.key!=="s"||(e.preventDefault(),e.stopImmediatePropagation(),ye())};document.addEventListener("keydown",Fe,!0),m(()=>document.removeEventListener("keydown",Fe,!0));const Ue=()=>{if(!oe.peek())return;const e=Me();try{S.set(w,{form:Ee(O(),e),base:N(Ee(e,e)),at:new Date().toISOString()})}catch{}},Oe=e=>{oe.peek()&&(Ue(),e.preventDefault(),e.returnValue="")};window.addEventListener("beforeunload",Oe),m(()=>window.removeEventListener("beforeunload",Oe)),A={key:Ze,dirty:()=>oe.peek(),dispose(){Z=!0,Ue(),T.splice(0).reverse().forEach(e=>{try{e()}catch{}})}},I.scan(l);const yt=await L("/actions/_count").catch(()=>null);if(!C())return;if(yt?.available===!1){H.value=!0,F.value=!1,I.scan(l);return}ht();const[Pe,Be,bt]=await Promise.all([L("/collections").catch(()=>[]),L("/projects").catch(()=>[]),L("/collections/roles/entries?limit=1000").catch(()=>({entries:[]}))]);if(!C())return;for(const e of Array.isArray(Pe)?Pe:[])P.appendChild(f(e.slug,`${e.title||e.slug} (${e.slug})`));for(const e of Array.isArray(Be)?Be:[])z.appendChild(f(e.slug,e.name||e.slug));if(Le.value=(bt?.entries||[]).map(e=>e.data||{}).filter(e=>e.name),P.value=i.collection.peek(),z.value=i.project.peek(),_){try{p=await L(`/actions/${encodeURIComponent(x)}`)}catch(t){if(!C())return;if(We(t)){H.value=!0,F.value=!1;return}E.toast(t.status===404||/not found/i.test(t.message||"")?`There is no action "${x}".`:`Could not load the action: ${t.message||t}`,{type:"error"}),F.value=!1,location.hash="#/actions";return}if(!C())return;if(!p){E.toast(`There is no action "${x}".`,{type:"error"}),location.hash="#/actions";return}ne=p.meta||{},p.collection&&![...P.options].some(t=>t.value===p.collection)&&P.appendChild(f(p.collection,`${p.collection} (not found)`));const e=X(p);e.project&&![...z.options].some(t=>t.value===e.project)&&z.appendChild(f(e.project,e.project)),le(e),P.value=i.collection.peek(),z.value=i.project.peek(),V.value=N(O()),ke(y,p.title||x)}else le(K),V.value=N(O());F.value=!1,I.scan(l);let B=null;try{B=S.get(w)}catch{}if(B?.form){try{S.remove?S.remove(w):S.set(w,null)}catch{}const e=Me(),t=B.base&&B.base!==N(Ee(e,e)),n=B.at?` (${D(B.at).format("D MMM HH:mm")})`:"",o=_?"this action":"a new action",r=t?`You left unsaved changes to ${o}${n}, but it has been saved since. Restore your version anyway?`:`You left unsaved changes to ${o}${n}. Restore them?`;if(await E.confirm(r)&&C()){const{form:a,lost:s}=Ct(B.form,X(p,{project:ue}));s&&E.toast(`${s===1?"A webhook URL":`${s} webhook URLs`} typed before you left ${s===1?"was":"were"} not kept in the draft - enter ${s===1?"it":"them"} again.`,{type:"warning",duration:8e3}),a.collection&&![...P.options].some(d=>d.value===a.collection)&&P.appendChild(f(a.collection,a.collection)),le(a)}}}};
@@ -1,3 +1,2 @@
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"?" - 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 - no credentials"],["token","Token - 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 - 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 - 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 - Send tests the saved definition.",{type:"info"}))}O&&document.removeEventListener("keydown",O),O=H,document.addEventListener("keydown",H),Y(),Domma.icons.scan(v)}};
1
+ import{api as _}from"../api.js";import{addBannerAction as Ue}from"../lib/plugin-chrome.js";import{helpPopovers as Et}from"../lib/help-popover.js";import{openMenuAt as Ye,sidebarCount as Ve}from"../lib/tool-kit.js";import{OPERATORS as xt,PSEUDO_FIELDS as kt,checkEndpoint as We,filterFromRows as Ct,normaliseDraft as wt,pathParams as Xe,placeholders as St,publicPath as jt,snapshot as G,splitFilterKey as qt,statusTone as ze,tryRequest as At}from"../lib/api-endpoint-editor-arrange.js";const Rt=/^\/api-endpoints\/(new|edit\/[^/?#]+)\/?(\?.*)?$/,Q=d=>String(d||"").split("?")[0].replace(/\/$/,""),$t="api-endpoint-editor.draft.",Lt=12;function Nt(d){return String(d??"").replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")}const j={get(d){try{return S.get(d)}catch{return null}},set(d,p){try{S.set(d,p)}catch{}},remove(d){try{S.remove?S.remove(d):S.set(d,null)}catch{}}};function Mt(d,p){const n=d.querySelector(".dm-plugin-banner-name");if(!n||!n.querySelector(".dm-plugin-banner-sep"))return;const T=n.lastChild;T&&T.nodeType===3?T.textContent=p:n.appendChild(document.createTextNode(p))}let L=null,Ge=!1;function Tt(){Ge||!window.R?.use||(Ge=!0,R.use((d,p,n)=>!(Rt.test(p?.path||"")&&Q(d?.path)!==Q(p?.path))||!L?.dirty()?n():E.confirm("This endpoint has unsaved changes. Leave anyway? They are kept as a draft for your next visit.").then(x=>{x?n():history.replaceState(null,"","#"+p.path)})),M.subscribe("router:afterChange",({to:d}={})=>{if(L&&Q(d?.path)!==L.path){const p=L;L=null,p.dispose()}}))}export const apiEndpointEditorView={templateUrl:"/admin/js/templates/api-endpoint-editor.html",async onMount(d){if(Tt(),L){const e=L;L=null,e.dispose()}const p=d.get(0),n=e=>p.querySelector("#"+e)||document.getElementById(e),T=[],x=e=>(T.push(e),e);let F=!1;const ke=Q(location.hash.slice(1)),Z=()=>!F&&p.isConnected&&Q(location.hash.slice(1))===ke,Ce=location.hash.split("?")[0].match(/\/api-endpoints\/edit\/([^/?#]+)/),w=Ce?decodeURIComponent(Ce[1]):null,B=$t+(w||"new"),ee=`apitryit:hist:${w||"new"}`,Qe=E.tabs(n("ep-editor-tabs"),{tabSelector:".tab-item",panelSelector:".tab-panel"}),Ze=E.loader(p,{type:"dots"}),[et,tt,nt,y]=await Promise.all([_.projects.list().catch(()=>[]),_.collections.list().catch(()=>[]),_.get("/collections/roles/entries?limit=1000").catch(()=>({entries:[]})),w?_.apiEndpoints.get(w).catch(()=>null):Promise.resolve(null)]);if(Ze.destroy(),!Z())return;if(w&&!y){E.toast("Endpoint not found.",{type:"error"}),location.hash="#/api-endpoints";return}const ot=tt.filter(e=>!e.systemManaged&&!e.preset),at=(nt.entries||[]).map(e=>e.data?.name).filter(Boolean),N=n("endpoint-project");for(const e of et){const t=document.createElement("option");t.value=e.slug,t.textContent=`${e.name} (${e.slug})`,N.appendChild(t)}const k=n("ep-collection"),we=e=>e.meta&&e.meta.project||"core";function pe(e){const t=e!==void 0?e:k.value,o=N.value,a=ot.filter(r=>{const s=we(r);return s===o||s==="core"});k.textContent="";for(const r of a){const s=document.createElement("option");s.value=r.slug,s.textContent=`${r.title} (${r.slug})${we(r)==="core"&&o!=="core"?" - core":""}`,k.appendChild(s)}if(!a.length){const r=document.createElement("option");r.value="",r.textContent="No collections available in this project",k.appendChild(r)}t&&[...k.options].some(r=>r.value===t)&&(k.value=t)}pe();const K=n("ep-auth");for(const[e,t]of[["public","Public - no credentials"],["token","Token - project API token"]]){const o=document.createElement("option");o.value=e,o.textContent=t,K.appendChild(o)}for(const e of at){const t=document.createElement("option");t.value=e,t.textContent=`Role: ${e}`,K.appendChild(t)}const de=M.observable(0),f=()=>{de.value=de.peek()+1},Y=M.computed(()=>(de.value,H()));let ue=null;const P=M.observable(""),q=M.observable(y||null),V=M.observable(!1),te=M.observable(!1),h=M.computed(()=>G(Y.value)!==P.value),ne=M.computed(()=>We(Y.value)),rt=M.computed(()=>V.value?ne.value:{}),st=M.computed(()=>(V.value||Y.value.path)&&ne.value.path||""),it=M.computed(()=>{const e=Object.keys(ne.value).length;return e?`${e} thing${e===1?"":"s"} to fix before it can be saved.`:"Save to put these changes live."});let O=[];async function oe(){const e=k.value;if(O=[],e)try{O=((await _.collections.get(e))?.fields||[]).map(o=>o.name).filter(Boolean)}catch{}Se(me());for(const t of A.querySelectorAll(".epx-frow"))he(t.querySelector(".ep-f-field"));je()}const fe=()=>[...O,...kt],me=()=>[...p.querySelectorAll(".ep-field-cb:checked")].map(e=>e.value);function Se(e=[]){const t=n("ep-fields-list");t.textContent="";for(const o of O){const a=document.createElement("label"),r=document.createElement("input");r.type="checkbox",r.className="ep-field-cb",r.value=o,r.checked=e.includes(o),r.addEventListener("change",()=>{f(),ye()}),a.appendChild(r),a.appendChild(document.createTextNode(o)),t.appendChild(a)}ye()}function ye(){const e=me().length;n("ep-fields-note").textContent=O.length?e?`Returns ${e} of ${O.length} fields.`:"None ticked - every field is returned.":"No fields found on this collection."}function je(e){const t=n("ep-sort"),o=e!==void 0?e:t.value;t.textContent="";for(const a of["createdAt",...fe()]){const r=document.createElement("option");r.value=a==="createdAt"?"":a,r.textContent=a==="createdAt"?"createdAt (default)":a,t.appendChild(r)}t.value=o||""}function he(e,t){const o=t!==void 0?t:e.value;e.textContent="";for(const r of fe()){const s=document.createElement("option");s.value=r,s.textContent=r,e.appendChild(s)}const a=document.createElement("option");if(a.value="__custom__",a.textContent="custom dot-path\u2026",e.appendChild(a),o&&o!=="__custom__"&&[...e.options].some(r=>r.value===o))e.value=o;else if(o&&o!=="__custom__"){const r=document.createElement("option");r.value=o,r.textContent=o,e.insertBefore(r,a),e.value=o}}const A=n("ep-filter-rows");function ae(e="",t="_eq",o="",a=null){const r=document.createElement("div");r.className="epx-frow",r.tabIndex=0;const s=document.createElement("select");s.className="form-input ep-f-field",s.setAttribute("aria-label","Field"),he(s,e||void 0);let i=s.value;s.addEventListener("change",async()=>{if(s.value==="__custom__"){const c=await E.prompt("Dot-path field (e.g. address.city):",{inputPlaceholder:"address.city"});c&&String(c).trim()?he(s,String(c).trim()):s.value=i||fe()[0]||""}i=s.value,f()});const l=document.createElement("select");l.className="form-input ep-f-op",l.setAttribute("aria-label","Operator");for(const[c,J]of xt){const C=document.createElement("option");C.value=c,C.textContent=J,l.appendChild(C)}l.value=t,l.addEventListener("change",f);const u=document.createElement("input");u.type="text",u.className="form-input font-mono ep-f-value",u.setAttribute("aria-label","Value"),u.placeholder="a literal, {{params.date}} or {{query.team}}",u.value=o,u.addEventListener("input",f);const g=document.createElement("button");return g.type="button",g.className="epx-kebab",g.title="Actions",g.setAttribute("aria-label","Filter actions"),g.textContent="\u22EE",r.append(s,l,u,g),A.insertBefore(r,a),ve(),r}const ve=()=>{n("ep-filter-empty").hidden=!!A.querySelector(".epx-frow")};function be(){return[...A.querySelectorAll(".epx-frow")].map(e=>({field:e.querySelector(".ep-f-field").value,op:e.querySelector(".ep-f-op").value,value:e.querySelector(".ep-f-value").value}))}function H(){return{name:n("ep-name").value.trim(),project:N.value,path:n("ep-path").value.trim(),collection:k.value,auth:K.value,mode:n("ep-mode").value,filter:Ct(be()),sort:n("ep-sort").value||null,order:n("ep-order").value,limit:Number(n("ep-limit").value===""?0:n("ep-limit").value),fields:me(),enabled:n("ep-enabled").checked}}async function ge(e){if(n("ep-name").value=e.name||"",!w&&e.project&&[...N.options].some(t=>t.value===e.project)&&(N.value=e.project),n("ep-path").value=e.path||"",pe(e.collection),e.collection&&k.value!==e.collection){const t=document.createElement("option");t.value=e.collection,t.textContent=`${e.collection} (other project - no longer allowed)`,k.appendChild(t),k.value=e.collection}[...K.options].some(t=>t.value===e.auth)&&(K.value=e.auth),n("ep-mode").value=e.mode||"list",n("ep-order").value=e.order||"desc",n("ep-limit").value=e.limit??50,n("ep-enabled").checked=e.enabled!==!1,await oe(),Se(e.fields||[]),je(e.sort||""),A.textContent="";for(const[t,o]of Object.entries(e.filter||{})){const{field:a,op:r}=qt(t);ae(a,r,String(o))}ve(),f()}x(M.effect(()=>{const e=Y.value;ue=e,n("ep-url-preview").textContent=jt(e.project,e.path||"");const t=ne.value,o=V.value;n("ep-def-dot").hidden=!(o&&(t.name||t.path||t.project||t.collection)),n("ep-query-dot").hidden=!(o&&(t.filter||t.limit))&&!(e.path&&t.filter);const a=n("ep-filter-err");a.textContent=t.filter||"",a.hidden=!t.filter;const r=n("ep-limit-err");r.textContent=t.limit||"",r.hidden=!t.limit}));const lt=e=>Mt(p,e||(w?"Edit":"New endpoint"));x(M.effect(()=>{const e=Y.value.name;lt(e||q.value?.name||"")}));function ct(){const e=q.peek(),t=h.peek()||!e;n("tryit-dirty-note").style.display=h.peek()?"":"none",n("tryit-send").disabled=t,n("tryit-token-group").style.display=e?.auth==="token"?"":"none";const o=n("tryit-params"),a={};for(const i of o.querySelectorAll("input"))a[i.dataset.param]=i.value;o.textContent="";for(const i of Xe(e?.path||""))o.appendChild(qe(`:${i}`,"param",i,a[i]));const r=n("tryit-query-params"),s={};for(const i of r.querySelectorAll("input"))s[i.dataset.queryParam]=i.value;r.textContent="";for(const i of St(e?.filter,"query"))r.appendChild(qe(`?${i}`,"queryParam",i,s[i],`optional - filters by ${i}`))}function qe(e,t,o,a="",r=""){const s=document.createElement("div");s.className="form-group";const i=document.createElement("label");i.className="form-label",i.textContent=e;const l=document.createElement("input");return l.type="text",l.className="form-input",l.dataset[t]=o,l.value=a,r&&(l.placeholder=r),s.append(i,l),s}x(M.effect(()=>{h.value,q.value,ct()}));let Ae="";const W=()=>{const e=j.get(ee);return Array.isArray(e)?e:[]};function Re(e){const t=W().filter(o=>o.url!==e.url);j.set(ee,[e,...t].slice(0,Lt)),re()}function $e(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||"",se()}function re(){const e=n("tryit-history-list"),t=W();n("tryit-history-card").style.display=t.length?"":"none",e.textContent="",t.forEach((o,a)=>{const r=document.createElement("div");r.className="epx-hrow",r.tabIndex=0,r.dataset.index=String(a);const s=document.createElement("span");s.className=`epx-chip ${ze(o.status,o.ok)}`,s.textContent=o.status||"ERR";const i=document.createElement("span");i.className="epx-url",i.textContent=o.url.replace(`/api/x/${q.peek()?.project||""}`,"")||o.url,i.title=o.url;const l=document.createElement("button");l.type="button",l.className="epx-kebab",l.title="Actions",l.setAttribute("aria-label","Request actions"),l.textContent="\u22EE",r.append(s,i,l),e.appendChild(r)})}const v={key:"var(--dm-info)",index:"var(--dm-text-muted)",string:"var(--dm-success)",number:"var(--dm-warning)",boolean:"var(--dm-secondary)",nul:"var(--dm-danger)",punc:"var(--dm-text-muted)",preview:"var(--dm-text-muted)"};function b(e,t,o){const a=document.createElement("span");return a.style.color=e,o&&(a.style.cssText+=";"+o),a.textContent=t,a}const Le=(e,t)=>t?b(v.index,String(e)):b(v.key,JSON.stringify(String(e)));function pt(e){return e===null?b(v.nul,"null"):typeof e=="number"?b(v.number,String(e)):typeof e=="boolean"?b(v.boolean,String(e)):b(v.string,JSON.stringify(e))}function Ne(e,t,o){const a=e&&typeof e=="object",r=a&&(Array.isArray(e)?e.length===0:Object.keys(e).length===0);if(!a||r){const m=document.createElement("div");return m.style.cssText="white-space:pre-wrap;word-break:break-word;padding-left:1.2em;",t!==void 0&&(m.appendChild(Le(t,o)),m.appendChild(b(v.punc,": "))),m.appendChild(a?b(v.punc,Array.isArray(e)?"[]":"{}"):pt(e)),m}const s=Array.isArray(e),i=s?e.map((m,z)=>[z,m,!0]):Object.entries(e).map(([m,z])=>[m,z,!1]),l=document.createElement("div"),u=document.createElement("div");u.style.cssText="white-space:pre-wrap;cursor:pointer;";const g=b(v.punc,"\u25BE","display:inline-block;width:1.2em;");u.appendChild(g),t!==void 0&&(u.appendChild(Le(t,o)),u.appendChild(b(v.punc,": "))),u.appendChild(b(v.punc,s?"[":"{"));const c=b(v.preview,` ${i.length} ${s?"item":"key"}${i.length===1?"":"s"} `,"font-style:italic;"),J=b(v.punc,s?"]":"}");u.append(c,J),l.appendChild(u);const C=document.createElement("div");C.style.cssText="padding-left:1.2em;border-left:1px solid var(--dm-border);";for(const[m,z,gt]of i)C.appendChild(Ne(z,m,gt));l.appendChild(C);const U=document.createElement("div");U.style.paddingLeft="1.2em",U.appendChild(b(v.punc,s?"]":"}")),l.appendChild(U);let ce=!1;const Je=m=>{ce=m,g.textContent=m?"\u25B8":"\u25BE",c.style.display=m?"":"none",J.style.display=m?"":"none",C.style.display=m?"none":"",U.style.display=m?"none":""};return Je(!1),u.addEventListener("click",()=>Je(!ce)),l}let Me="";function Te(e){n("tryit-json").style.display=e?"":"none",n("tryit-body").style.display=e?"none":"",n("tryit-json-toggle").textContent=e?"Raw":"Tree"}function Pe(e){Me=e,n("tryit-results-placeholder").style.display="none",n("tryit-response-card").style.display="";let t,o=!0;try{t=JSON.parse(e)}catch{o=!1}n("tryit-body").textContent=o?JSON.stringify(t,null,2):e;const a=n("tryit-json");a.textContent="",o&&a.appendChild(Ne(t)),n("tryit-json-toggle").style.display=o?"":"none",Te(o)}async function se(){const e=q.peek();if(!e||h.peek())return;const t={},o={};for(const c of n("tryit-params").querySelectorAll("input"))t[c.dataset.param]=c.value;for(const c of n("tryit-query-params").querySelectorAll("input"))o[c.dataset.queryParam]=c.value;const a=n("tryit-query").value.trim(),{url:r,headers:s,curl:i}=At(e,{params:t,queryParams:o,extra:a,token:n("tryit-token").value.trim(),origin:location.origin});Ae=i;const l={ts:Date.now(),url:r,params:t,queryParams:o,extra:a};n("tryit-result").style.display="",n("tryit-curl").textContent=i;const u=n("tryit-send");u.disabled=!0;const g=n("tryit-status");try{const c=await fetch(r,{headers:s}),J=await c.text();if(F)return;g.textContent=`${c.status} ${c.statusText}`,g.className=`epx-chip ${ze(c.status,c.ok)}`;const C=[];c.headers.forEach((U,ce)=>C.push(`${ce}: ${U}`)),n("tryit-headers-group").style.display="",n("tryit-headers").textContent=C.length?C.join(`
2
+ `):"(none exposed)",Pe(J),Re({...l,status:c.status,statusText:c.statusText,ok:c.ok})}catch(c){if(F)return;g.textContent="Network error",g.className="epx-chip is-danger",n("tryit-headers-group").style.display="none",Pe(String(c.message||c)),Re({...l,status:0,statusText:"Network error",ok:!1})}finally{F||(u.disabled=h.peek()||!q.peek())}}async function ie(e,t){if(!e){E.toast("Send a request first.",{type:"info"});return}try{await navigator.clipboard.writeText(e),E.toast(`${t} copied.`,{type:"success"})}catch{E.toast("Copy failed.",{type:"error"})}}async function le({thenSend:e=!1}={}){if(te.peek())return!1;V.value=!0;const t=H(),o=We(t);if(Object.keys(o).length){const a=Object.keys(o)[0];return Qe.activate?.(["filter","limit"].includes(a)?1:0),E.toast(o[a],{type:"error"}),!1}te.value=!0;try{if(w){const a=await _.apiEndpoints.update(w,t);if(F)return!0;q.value=a,$=H(),P.value=G($),j.remove(B),E.toast("Endpoint saved.",{type:"success"}),Ve("#/api-endpoints"),e&&se()}else{const a=await _.apiEndpoints.create(t);$=H(),P.value=G($),j.remove(B),E.toast("Endpoint created.",{type:"success"}),Ve("#/api-endpoints"),location.hash="#/api-endpoints/edit/"+encodeURIComponent(a.id)}return!0}catch(a){return E.toast(`Save failed: ${a.message||a}`,{type:"error"}),!1}finally{te.value=!1}}let $=null;async function dt(){!h.peek()||!await E.confirm("Discard the changes you have not saved?")||Z()&&(await ge($),V.value=!1,j.remove(B))}function ut(e){if(!e)return[];const t=[...A.querySelectorAll(".epx-frow")],o=t.indexOf(e),a=Xe(n("ep-path").value),r=e.querySelector(".ep-f-value");return[{header:`Filter ${o+1}`},{label:"Move up",icon:"arrow-up",disabled:o<=0,action:()=>{A.insertBefore(e,t[o-1]),f()}},{label:"Move down",icon:"arrow-down",disabled:o>=t.length-1,action:()=>{A.insertBefore(t[o+1],e),f()}},{label:"Duplicate",icon:"copy",action:()=>{const s=be()[o];ae(s.field,s.op,s.value,e.nextSibling),f()}},...a.length?[{type:"divider"},...a.map(s=>({label:`Use :${s}`,icon:"link",action:()=>{r.value=`{{params.${s}}}`,f()}}))]:[],{type:"divider"},{label:"Remove",icon:"trash",danger:!0,action:()=>{e.remove(),ve(),f()}}]}const ft=E.contextMenu(A,{cursor:"glyph",match:".epx-frow",inherit:!1,density:"compact",exclude:"input, select, textarea",items:e=>ut(e)});x(()=>ft?.destroy?.());const Pt=n("ep-fields-list"),Ee=e=>{for(const t of p.querySelectorAll(".ep-field-cb"))t.checked=e(t.checked);f(),ye()},mt=E.contextMenu(n("ep-fields-more").parentElement.parentElement,{cursor:"glyph",match:"#ep-fields-list, #ep-fields-more",inherit:!1,density:"compact",items:()=>O.length?[{header:"Response fields"},{label:"Tick all",icon:"check",action:()=>Ee(()=>!0)},{label:"Tick none (return everything)",icon:"x",action:()=>Ee(()=>!1)},{label:"Invert",icon:"refresh-cw",action:()=>Ee(e=>!e)}]:[]});x(()=>mt?.destroy?.());const Ie=n("tryit-history-list"),yt=e=>{const t=W()[Number(e?.dataset.index)];if(!t)return[];const o=q.peek();return[{header:`${t.status||"ERR"} ${t.statusText||""}`.trim()},{label:"Run again",icon:"play",disabled:h.peek()||!o,action:()=>$e(t)},{label:"Copy address",icon:"link",action:()=>ie(location.origin+t.url,"Address")},{label:"Copy as curl",icon:"copy",action:()=>ie(`curl '${(location.origin+t.url).replace(/'/g,"'\\''")}'`,"curl command")},{type:"divider"},{label:"Remove from history",icon:"trash",danger:!0,action:()=>{j.set(ee,W().filter(a=>a.url!==t.url)),re()}}]},ht=E.contextMenu(Ie,{cursor:"glyph",match:".epx-hrow",inherit:!1,density:"compact",items:yt});x(()=>ht?.destroy?.());const Oe=e=>{const t=e.target.closest(".epx-kebab");if(t&&p.contains(t)){Ye(t);return}const o=e.target.closest(".epx-hrow");if(o&&Ie.contains(o)){const a=W()[Number(o.dataset.index)];if(!a)return;if(h.peek()){E.toast("Save first - Send tests the saved definition.",{type:"info"});return}$e(a)}},_e=e=>{if(e.key==="F10"&&e.shiftKey){const t=e.target.closest(".epx-frow, .epx-hrow, #ep-fields-list");t&&p.contains(t)&&(e.preventDefault(),Ye(t))}else e.key==="Enter"&&e.target.classList?.contains("epx-hrow")&&(e.preventDefault(),e.target.click())};if(p.addEventListener("click",Oe),p.addEventListener("keydown",_e),x(()=>{p.removeEventListener("click",Oe),p.removeEventListener("keydown",_e)}),y?(N.value=y.project,N.disabled=!0,await ge({name:y.name||"",project:y.project,path:y.path||"",collection:y.collection,auth:y.auth||"public",mode:y.mode||"list",filter:y.filter||{},sort:y.sort||null,order:y.order||"desc",limit:y.limit??50,fields:y.fields||[],enabled:y.enabled!==!1})):(await oe(),ae(),f()),!Z())return;$=H(),P.value=G($);for(const e of["ep-name","ep-path","ep-limit"])n(e).addEventListener("input",f);for(const e of["ep-mode","ep-order","ep-sort","ep-enabled"])n(e).addEventListener("change",f);K.addEventListener("change",f),N.addEventListener("change",async()=>{pe(),await oe(),f(),!w&&!n("ep-name").value.trim()&&!n("ep-path").value.trim()&&!be().some(e=>e.value.trim())&&($=H(),P.value=G($))}),k.addEventListener("change",async()=>{await oe(),f()}),n("ep-add-filter").addEventListener("click",()=>{ae().querySelector(".ep-f-value").focus(),f()}),n("tryit-send").addEventListener("click",se),n("tryit-save-send").addEventListener("click",()=>le({thenSend:!0})),n("tryit-history-clear").addEventListener("click",async()=>{await E.confirm("Clear this endpoint's test history?")&&(j.set(ee,[]),re())}),n("tryit-json-toggle").addEventListener("click",()=>Te(n("tryit-json").style.display==="none")),n("tryit-body-copy").addEventListener("click",()=>ie(Me,"Response")),n("tryit-copy-curl").addEventListener("click",()=>ie(Ae,"curl command"));const De={dirty:h,saving:te,barText:it,err:rt,pathErr:st},Fe={save:()=>le(),discard:()=>dt()},vt=M.applyBindings(De,n("ep-bar"),{methods:Fe}),bt=M.applyBindings(De,n("ep-def-panel"),{methods:Fe});x(()=>{vt.dispose(),bt.dispose()}),x(Et(p,{position:"bottom"})),Ue(d,{id:"ep-back",icon:"arrow-left",title:"All endpoints",onClick:()=>{location.hash="#/api-endpoints"}}),Ue(d,{id:"ep-save",icon:"save",title:"Save (Ctrl+S)",onClick:()=>le()});const Be=e=>{if(!(e.metaKey||e.ctrlKey))return;const o=e.key.toLowerCase();o==="s"?(e.preventDefault(),e.stopImmediatePropagation(),le()):o==="enter"&&(e.preventDefault(),q.peek()&&!h.peek()?se():h.peek()&&E.toast("Save first - Send tests the saved definition.",{type:"info"}))};document.addEventListener("keydown",Be,!0),x(()=>document.removeEventListener("keydown",Be,!0));const Ke=()=>{!h.peek()||!ue||j.set(B,{state:ue,base:P.peek(),at:new Date().toISOString()})},He=e=>{h.peek()&&(Ke(),e.preventDefault(),e.returnValue="")};window.addEventListener("beforeunload",He),x(()=>window.removeEventListener("beforeunload",He)),L={path:ke,dirty:()=>h.peek(),dispose(){Ke(),F=!0,T.splice(0).reverse().forEach(e=>{try{e()}catch{}})}},re(),I.scan(p);const X=j.get(B),xe=wt(X);if(xe){j.remove(B);const e=X.base&&X.base!==P.peek(),t=X.at?` (${D(X.at).format("D MMM HH:mm")})`:"";await E.confirm(`You left unsaved changes to this endpoint${Nt(t)}.${e?" It has been saved since - restoring puts your older changes over it.":""} Restore them?`)&&Z()&&(w&&(xe.project=y.project),await ge(xe))}}};
@@ -1,8 +1,14 @@
1
- import{api as f}from"../api.js";let u=null,h=!1,v={};function g(){h=!0;const e=document.getElementById("block-quick-switch");e&&(e.disabled=!0)}function x(){h=!1;const e=document.getElementById("block-quick-switch");e&&(e.disabled=!1)}function C(e,t){if(e==null||e==="")return null;const n=e instanceof Date?e:new Date(e);if(isNaN(n.getTime()))return null;const c=(s,o=2)=>String(s).padStart(o,"0"),a=["January","February","March","April","May","June","July","August","September","October","November","December"],l=["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],i={YYYY:()=>n.getFullYear(),YY:()=>c(n.getFullYear()%100),MMMM:()=>a[n.getMonth()],MMM:()=>a[n.getMonth()].slice(0,3),MM:()=>c(n.getMonth()+1),M:()=>n.getMonth()+1,DD:()=>c(n.getDate()),D:()=>n.getDate(),dddd:()=>l[n.getDay()],ddd:()=>l[n.getDay()].slice(0,3),HH:()=>c(n.getHours()),H:()=>n.getHours(),hh:()=>c((n.getHours()+11)%12+1),h:()=>(n.getHours()+11)%12+1,mm:()=>c(n.getMinutes()),m:()=>n.getMinutes(),ss:()=>c(n.getSeconds()),s:()=>n.getSeconds(),A:()=>n.getHours()<12?"AM":"PM",a:()=>n.getHours()<12?"am":"pm"};return t.replace(/YYYY|YY|MMMM|MMM|MM|M|DD|D|dddd|ddd|HH|H|hh|h|mm|m|ss|s|A|a/g,s=>String(i[s]()))}export const blockEditorView={templateUrl:"/admin/js/templates/block-editor.html",async onMount(e){I.register("edit-2",{viewBox:"0 0 24 24",path:"M17 3a2.85 2.85 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5z",stroke:"currentColor",fill:"none",strokeWidth:2,strokeLinecap:"round",strokeLinejoin:"round"}),requestAnimationFrame(()=>{document.querySelectorAll('[data-icon="edit-2"]').forEach(l=>{if(l.tagName.toLowerCase()==="svg")return;const i=I.render("edit-2",{size:parseInt(l.dataset.iconSize,10)||24});i&&l.replaceWith(i)})}),u=null,h=!1,v={};const t=e.find("#block-project").get(0);if(t)try{(await f.projects.list()).forEach(i=>{const s=document.createElement("option");s.value=i.slug,s.textContent=i.name||i.slug,t.appendChild(s)})}catch{}const n=window.location.hash.match(/\/blocks\/edit\/([^/?#]+)/);n&&(u=decodeURIComponent(n[1]));const c=e.find("#block-name").get(0),a=e.find("#block-content").get(0);if(u){e.find("#block-editor-title").text("Edit Block"),c&&(c.value=u,c.disabled=!0);try{const l=await f.blocks.get(u);a&&(a.value=l.content||""),e.find("#block-bundled").prop("checked",!!l.bundled),e.find("#block-css").val(l.css||""),v=l.meta||{},e.find("#block-project").val(l.meta?.project||"");try{const i=await f.blocks.list(),s=e.find("#block-quick-switch").get(0);s&&Array.isArray(i)&&i.length&&(i.slice().sort((o,r)=>o.name.localeCompare(r.name)).forEach(o=>{const r=document.createElement("option");r.value=o.name,r.textContent=o.name,o.name===u&&(r.selected=!0),s.appendChild(r)}),s.style.display="",s.addEventListener("change",function(){const o=this.value;o&&o!==u&&!h&&R.navigate(`/blocks/edit/${encodeURIComponent(o)}`)}))}catch{}}catch(l){E.toast(l.message||"Block not found.",{type:"error"}),R.navigate("/blocks");return}}a&&(L(a,e),$(a,e)),e.find("#save-block-btn").off("click").on("click",async()=>{await q(e)}),Domma.icons.scan()}};function L(e,t){const n=t.find("#block-line-numbers").get(0),c=t.find("#block-cursor-pos").get(0);function a(){const s=e.value.split(`
2
- `).length;n.textContent=Array.from({length:s},(o,r)=>r+1).join(`
3
- `),n.scrollTop=e.scrollTop}a(),e.addEventListener("input",a),e.addEventListener("scroll",()=>{n.scrollTop=e.scrollTop});function l(){if(!c)return;const s=e.value.slice(0,e.selectionStart).split(`
4
- `);c.textContent=`Ln ${s.length}, Col ${s[s.length-1].length+1}`}e.addEventListener("keyup",l),e.addEventListener("click",l),e.addEventListener("keydown",s=>{if(s.key==="Tab"){s.preventDefault();const o=e.selectionStart,r=e.selectionEnd;if(!s.shiftKey)e.value=e.value.slice(0,o)+" "+e.value.slice(r),e.selectionStart=e.selectionEnd=o+2;else{const d=e.value.lastIndexOf(`
5
- `,o-1)+1,m=e.value.slice(d,d+2),p=m===" "?2:m[0]===" "?1:0;p>0&&(e.value=e.value.slice(0,d)+e.value.slice(d+p),e.selectionStart=e.selectionEnd=Math.max(d,o-p))}e.dispatchEvent(new Event("input"))}if(s.key==="Enter"){s.preventDefault();const o=e.selectionStart,r=e.value.lastIndexOf(`
6
- `,o-1)+1,d=e.value.slice(r,o).match(/^(\s*)/)[1];e.value=e.value.slice(0,o)+`
7
- `+d+e.value.slice(e.selectionEnd),e.selectionStart=e.selectionEnd=o+1+d.length,e.dispatchEvent(new Event("input"))}});const i=t.find("#block-editor-toolbar").get(0);i&&i.addEventListener("click",s=>{const o=s.target.closest("[data-action]");o&&(D(o.dataset.action,e),e.focus())})}function D(e,t){switch(e){case"undo":document.execCommand("undo");break;case"redo":document.execCommand("redo");break;case"cut":t.selectionStart!==t.selectionEnd&&(navigator.clipboard.writeText(t.value.slice(t.selectionStart,t.selectionEnd)).catch(()=>{}),document.execCommand("cut"));break;case"copy":t.selectionStart!==t.selectionEnd&&navigator.clipboard.writeText(t.value.slice(t.selectionStart,t.selectionEnd)).catch(()=>{});break;case"paste":navigator.clipboard.readText().then(n=>{const c=t.selectionStart;t.value=t.value.slice(0,c)+n+t.value.slice(t.selectionEnd),t.selectionStart=t.selectionEnd=c+n.length,t.dispatchEvent(new Event("input"))}).catch(()=>E.toast("Use Ctrl+V to paste.",{type:"info"}));break;case"select-all":t.select();break;case"format":T(t);break}}function T(e){const t=e.value.match(/(<[^>]+>|[^<]+)/g)||[],n=[];let c=0;const a=" ";for(const s of t){const o=s.trim();if(o)if(o.startsWith("</"))c=Math.max(0,c-1),n.push(a.repeat(c)+o);else if(o.startsWith("<")&&!o.startsWith("<!")&&!o.endsWith("/>")){n.push(a.repeat(c)+o);const r=(o.match(/^<(\w+)/)||[])[1]||"";j.has(r.toLowerCase())||c++}else n.push(a.repeat(c)+o)}const l=n.join(`
8
- `),i=e.selectionStart;e.value=l,e.selectionStart=e.selectionEnd=Math.min(i,l.length),e.dispatchEvent(new Event("input"))}const j=new Set(["area","base","br","col","embed","hr","img","input","link","meta","param","source","track","wbr"]);function $(e,t){const n=t.find("#block-editor-body").get(0),c=t.find("#block-sample-data").get(0),a=t.find("#block-preview-output");t.find("[data-mode]").each(function(){this.addEventListener("click",()=>{t.find("[data-mode]").each(function(){this.classList.remove("active")}),this.classList.add("active"),n.className=`editor-body editor-mode-${this.dataset.mode}`,this.dataset.mode!=="write"&&l()})}),e.addEventListener("input",()=>{M(c,e),l(),g()});{const i=t.find("#block-css").get(0);i&&i.addEventListener("input",()=>{l(),g()});const s=t.find("#block-name").get(0);s&&s.addEventListener("input",()=>{l(),g()});const o=t.find("#block-bundled").get(0);o&&o.addEventListener("change",g)}e.value.trim()&&(M(c,e),l());function l(){if(n.classList.contains("editor-mode-write"))return;const i=e.value.replace(/\{\{([\w_]+)(?::([^{}]+))?\}\}/g,(p,k,y)=>{const w=c.querySelector(`[data-placeholder="${CSS.escape(k)}"]`)?.value??`[${k}]`;if(y){const S=C(w,y);if(S!==null)return b(S)}return b(w)}),s=(t.find("#block-name").val()||"preview").trim()||"preview",o=b(s),r=t.find("#block-css").val()||"",d=t.find("#block-css-scope-hint").get(0);d&&(d.textContent=`[data-block="${s}"]`);const m=r.trim()?`<style>[data-block="${o}"] { ${String(r).replace(/<\/style/gi,"<\\/style")} }</style>`:"";a.html(m+`<div data-block="${o}">${i}</div>`,{safe:!1}),Domma.icons.scan(a.get(0))}c._renderPreview=l}function M(e,t){const n=Y(t.value),c=new Set([...e.querySelectorAll("[data-placeholder]")].map(a=>a.dataset.placeholder));if(n.size>0&&e.querySelector("p.text-muted")?.remove(),n.forEach(a=>{c.has(a)||A(e,a)}),e.querySelectorAll("[data-placeholder]").forEach(a=>{n.has(a.dataset.placeholder)||a.closest(".block-sample-row")?.remove()}),n.size===0&&!e.querySelector("p.text-muted")){const a=document.createElement("p");a.className="text-muted",a.style.cssText="font-size:.8rem;margin:0;",a.textContent="No {{placeholders}} detected in template.",e.appendChild(a)}}function A(e,t){const n=document.createElement("div");n.className="block-sample-row",n.style.cssText="display:flex;align-items:center;gap:.5rem;margin-bottom:.35rem;";const c=document.createElement("label");c.style.cssText="font-size:.75rem;color:var(--dm-text-muted,#888);white-space:nowrap;min-width:90px;text-align:right;flex-shrink:0;",c.textContent=`{{${t}}}`;const a=document.createElement("input");a.type="text",a.className="form-input form-input--sm",a.style.flex="1",a.dataset.placeholder=t,a.placeholder=`Sample ${t}`,a.value=H(t),a.addEventListener("input",()=>{e._renderPreview&&e._renderPreview()}),n.appendChild(c),n.appendChild(a),e.appendChild(n)}function H(e){const t=e.toLowerCase();return t==="_id"?"a1b2c3d4-e5f6-7890-abcd-ef1234567890":t==="_createdat"?new Date().toISOString():t==="_updatedat"?new Date().toISOString():t.includes("email")?"user@example.com":t.includes("phone")?"+44 7700 900000":t.includes("name")?"Jane Smith":t.includes("title")?"Sample Title":t.includes("message")||t.includes("content")||t.includes("description")?"This is a sample value for preview purposes.":t.includes("rating")?"excellent":t.includes("status")?"active":t.includes("priority")?"high":t.includes("date")?new Date().toLocaleDateString():t.includes("tag")?"tag1, tag2":t.includes("subject")?"Sample Subject":t.includes("category")?"general":`Sample ${e}`}function Y(e){const t=new Set;for(const[,n]of e.matchAll(/\{\{([\w_]+)(?::[^{}]+)?\}\}/g))t.add(n);return t}function b(e){return String(e??"").replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")}async function q(e){const t=e.find("#block-name").get(0),n=e.find("#block-content").get(0),c=(t?.value||"").trim(),a=n?.value??"";if(!c){E.toast("Block name is required.",{type:"warning"});return}if(!/^[a-z0-9][a-z0-9-]*$/.test(c)){E.toast("Name must start with a letter or digit and contain only lowercase letters, digits, and hyphens.",{type:"warning"});return}const l=!!e.find("#block-bundled").is(":checked"),i=e.find("#save-block-btn").get(0);i&&(i.disabled=!0);try{await f.blocks.put(c,{content:a,bundled:l,css:e.find("#block-css").val()||"",meta:{...v||{},project:e.find("#block-project").val()||""||null}}),x(),E.toast(u?"Block updated.":"Block created.",{type:"success"}),u||(u=c,R.navigate(`/blocks/edit/${encodeURIComponent(c)}`))}catch(s){E.toast(s.message||"Failed to save block.",{type:"error"})}finally{i&&(i.disabled=!1)}}
1
+ import{api as T}from"../api.js";import{addBannerAction as G}from"../lib/plugin-chrome.js";import{helpPopovers as Te}from"../lib/help-popover.js";import{openMenuAt as ye,sidebarCount as De}from"../lib/tool-kit.js";import{createSimpleEditor as ke}from"../lib/simple-editor.js";import{checkForm as Ie,draftKey as g,draftOffer as Fe,escapeHtml as Se,extractPlaceholders as Ne,formFromBlock as Pe,formatHtml as He,payloadFromForm as Ve,renderPreview as Be,sampleValue as ge,scopeName as Ke,shortcodeFor as Ye,snapshot as Q}from"../lib/block-editor-arrange.js";const Ue=/^\/blocks\/(new|edit\/[^/?#]+)\/?(\?.*)?$/,F=c=>String(c||"").split("?")[0],X="block-editor.mode",We=["write","split","preview"],_e=`
2
+ <div class="bex-sample" data-key="{{key}}" tabindex="0">
3
+ <label class="bex-sample-label" for="bex-sample-{{key}}">{{label}}</label>
4
+ <input id="bex-sample-{{key}}" type="text" class="form-input form-input--sm" data-bind-value="value" data-sample="{{key}}" placeholder="Sample {{key}}">
5
+ <button type="button" class="bex-kebab" aria-label="Actions for {{label}}" title="Actions">\u22EE</button>
6
+ </div>`;let p=null,we=!1;function ze(){we||!window.R?.use||(we=!0,R.use((c,i,s)=>{if(!(Ue.test(i?.path||"")&&F(c?.path)!==F(i?.path))||!p?.dirty())return s();const r=p;return E.confirm(`You have unsaved changes to ${Se(r.label())}. Leave anyway? They are kept as a draft for your next visit.`).then(A=>{A?s():history.replaceState(null,"",r.hash)})}),M.subscribe("router:afterChange",({to:c}={})=>{if(p&&F(c?.path)!==p.path){const i=p;p=null,i.dispose()}}))}function Je(c,i){const s=c.querySelector(".dm-plugin-banner-name"),f=s?.lastChild;f&&f.nodeType===3&&s.querySelector(".dm-plugin-banner-sep")&&(f.textContent=i)}function N(c){return navigator.clipboard?.writeText(c).then(()=>!0,()=>!1)??Promise.resolve(!1)}export const blockEditorView={templateUrl:"/admin/js/templates/block-editor.html",async onMount(c){if(ze(),p){const e=p;p=null,e.dispose()}const i=c.get(0),s=i.querySelector(".bex")||i,f=[],r=e=>(f.push(e),e);let A=!1;const P=()=>!A&&s.isConnected,Z=location.hash.match(/^#\/blocks\/edit\/([^/?#]+)/),a=Z?decodeURIComponent(Z[1]):null,l=!a,Ee=location.hash,Me=F(location.hash.slice(1)),Ce=l&&new URLSearchParams(location.hash.split("?")[1]||"").get("project")||"",ee=E.loader(i,{type:"dots"});let b=null,H=[],V=[];try{[b,H,V]=await Promise.all([a?T.blocks.get(a):Promise.resolve(null),T.blocks.list().catch(()=>[]),T.projects.list().catch(()=>[])])}catch(e){ee.destroy(),E.toast(e.message||"Block not found.",{type:"error"}),R.navigate("/blocks");return}if(ee.destroy(),!s.isConnected)return;const te=(Array.isArray(H)?H:[]).map(e=>e.name).sort((e,t)=>e.localeCompare(t));let oe=b?.meta||{};const u=Pe(b?{...b,name:a}:null,{project:Ce}),n={};for(const[e,t]of Object.entries(u))n[e]=M.observable(t);const B=()=>Object.fromEntries(Object.keys(n).map(e=>[e,n[e].peek()])),ae=()=>Object.fromEntries(Object.keys(n).map(e=>[e,n[e].value])),w=M.observable(Q(u)),C=M.computed(()=>Q(ae())!==w.value),x=M.observable(!1),O=M.observable(!1),q=M.computed(()=>Ie(ae(),{isNew:l,taken:te}));let ne="split";try{ne=We.includes(S.get(X))?S.get(X):"split"}catch{}const h=M.observable(ne);r(M.effect(()=>{const e=h.value;try{S.set(X,e)}catch{}}));const se=M.observable("Ln 1, Col 1"),y={},K=M.observable(0),Y=M.observable([]);r(M.effect(()=>{const e=Ne(n.content.value);for(const t of e)t in y||(y[t]=ge(t));Y.value=e.map(t=>({key:t,label:`{{${t}}}`,value:y[t]}))}));const xe={...n,dirty:C,saving:x,mode:h,cursor:se,sampleRows:Y,isNew:l,notNew:!l,titleText:l?"New block":a,metaText:M.computed(()=>{if(l)return"Not saved yet";const e=[];return(b?.updatedAt||b?.meta?.updatedAt)&&e.push(`changed ${D(b.updatedAt||b.meta.updatedAt).format("D MMM YYYY HH:mm")}`),e.push(`${n.content.value.split(`
7
+ `).length} lines`),n.css.value.trim()&&e.push(`${Math.ceil(n.css.value.length/1024)} KB CSS`),e.join(" \xB7 ")}),nameErr:M.computed(()=>(O.value||n.name.value)&&q.value.errors.name||""),cssErr:M.computed(()=>q.value.errors.css||""),scopeHint:M.computed(()=>`[data-block="${Ke(n.name.value)}"]`),noSamples:M.computed(()=>Y.value.length===0),noSamplesText:"No {{placeholders}} in the template yet.",modeCls:M.computed(()=>`bex-body is-${h.value}`),cannotSave:M.computed(()=>x.value||O.value&&Object.keys(q.value.errors).length>0)},U=s.querySelector("#block-project");for(const e of Array.isArray(V)?V:[]){const t=document.createElement("option");t.value=e.slug,t.textContent=e.name||e.slug,U.appendChild(t)}if(u.project&&![...U.options].some(e=>e.value===u.project)){const e=document.createElement("option");e.value=u.project,e.textContent=`${u.project} (no longer a project)`,U.appendChild(e)}const $=s.querySelector("#block-quick-switch");for(const e of te){const t=document.createElement("option");t.value=e,t.textContent=e,e===a&&(t.selected=!0),$.appendChild(t)}const m=s.querySelector(".bex-samples");m.innerHTML=_e;let k=null,j=null,o=null;function re(e){for(const[t,v]of Object.entries(e))n[t]&&(n[t].value=v);k.setValue(String(e.content??"")),j.setValue(String(e.css??""))}const ce=s.querySelector("#block-preview-output");let W=0;r(M.effect(()=>{const e={content:n.content.value,css:n.css.value,name:n.name.value};K.value,h.value!=="write"&&(cancelAnimationFrame(W),W=requestAnimationFrame(()=>{P()&&(ce.innerHTML=Be(e,y),I.scan(ce))}))})),r(()=>cancelAnimationFrame(W));const le=e=>{const t=e.target?.dataset?.sample;t&&(y[t]=e.target.value,K.value++)};m.addEventListener("input",le),r(()=>m.removeEventListener("input",le));function _(e){o.focus(),o.setRangeText(e,o.selectionStart,o.selectionEnd,"end"),o.dispatchEvent(new Event("input",{bubbles:!0}))}async function ie(e){switch(e){case"undo":o.focus(),document.execCommand("undo");break;case"redo":o.focus(),document.execCommand("redo");break;case"cut":o.selectionStart!==o.selectionEnd&&(await N(o.value.slice(o.selectionStart,o.selectionEnd)),_(""));break;case"copy":o.selectionStart!==o.selectionEnd&&await N(o.value.slice(o.selectionStart,o.selectionEnd));break;case"paste":try{_(await navigator.clipboard.readText())}catch{E.toast("Use Ctrl+V to paste.",{type:"info"})}break;case"select-all":o.focus(),o.select();break;case"format":{const t=He(o.value);k.setValue(t),n.content.value=t;break}default:break}}async function z(){if(x.peek())return;O.value=!0;const{errors:e,name:t}=q.peek();if(Object.keys(e).length){E.toast(e.name||e.css,{type:"warning"});return}const v=l?t:a;x.value=!0;try{const d=Ve(B(),oe);if(await T.blocks.put(v,d),!P())return;oe=d.meta,w.value=Q(B());try{S.remove?S.remove(g(a)):S.set(g(a),null)}catch{}De("#/blocks"),E.toast(l?`Created "${v}".`:"Block saved.",{type:"success"}),l&&(location.hash=`#/blocks/edit/${encodeURIComponent(v)}`)}catch(d){E.toast(`Not saved: ${d.message||d}`,{type:"error"})}finally{x.value=!1}}async function $e(){C.peek()&&await E.confirm("Discard the changes you have not saved?")&&(O.value=!1,re(je(w.peek())))}function je(e){try{const[t,v,d,Re,qe]=JSON.parse(e);return{name:t,project:v,bundled:d,content:Re,css:qe}}catch{return u}}const Le=M.applyBindings(xe,s,{methods:{save:()=>z(),discard:()=>$e(),setWrite:()=>{h.value="write"},setSplit:()=>{h.value="split"},setPreview:()=>{h.value="preview"},act:e=>ie(e)}});r(()=>Le.dispose()),r(Te(s,{position:"bottom"})),k=ke(s.querySelector(".bex-write"),{initialValue:u.content,language:"html",autoIndent:!0,onChange:e=>{n.content.value=e},onCursor:({line:e,col:t})=>{se.value=`Ln ${e}, Col ${t}`}}),j=ke(s.querySelector(".bex-css"),{initialValue:u.css,language:"css",autoIndent:!0,onChange:e=>{n.css.value=e}}),k.textarea.setAttribute("aria-label","Template HTML"),k.textarea.placeholder=`<div class="card mb-3">
8
+ <div class="card-body">
9
+ <h3>{{title}}</h3>
10
+ <p>{{message}}</p>
11
+ </div>
12
+ </div>`,j.textarea.setAttribute("aria-label","Custom CSS"),j.textarea.placeholder=`/* Scoped to this block - e.g.
13
+ color: var(--dm-primary);
14
+ h3 { font-size: 1.25rem; } */`,r(()=>{k.destroy(),j.destroy()}),o=k.textarea;const ue=s.querySelector(".bex-toolbar"),de=e=>{const t=e.target.closest("[data-action]");t&&ie(t.dataset.action)};ue.addEventListener("click",de),r(()=>ue.removeEventListener("click",de));const pe=()=>{const e=$.value;e&&e!==a?location.hash=`#/blocks/edit/${encodeURIComponent(e)}`:$.value=a||""};$.addEventListener("change",pe),r(()=>$.removeEventListener("change",pe));const Ae=e=>[{header:`{{${e}}}`},{label:"Insert into the template",icon:"corner-down-right",action:()=>_(`{{${e}}}`)},{label:"Copy placeholder",icon:"copy",action:async()=>{E.toast(await N(`{{${e}}}`)?"Copied.":`{{${e}}}`,{type:"info"})}},{label:"Reset sample value",icon:"rotate-ccw",action:()=>{y[e]=ge(e);const t=m.querySelector(`[data-sample="${CSS.escape(e)}"]`);t&&(t.value=y[e]),K.value++}}],Oe=E.contextMenu(m,{cursor:"glyph",match:".bex-sample",inherit:!1,density:"compact",items:e=>Ae(e.dataset.key)});r(()=>Oe?.destroy?.());const me=e=>{const t=e.target.closest(".bex-kebab");t&&ye(t)},ve=e=>{const t=e.target.closest(".bex-sample");t&&e.key==="F10"&&e.shiftKey&&(e.preventDefault(),ye(t))};m.addEventListener("click",me),m.addEventListener("keydown",ve),r(()=>{m.removeEventListener("click",me),m.removeEventListener("keydown",ve)}),Je(i,l?"New block":a),G(c,{id:"block-editor-list",icon:"arrow-left",title:"All blocks",onClick:()=>{location.hash="#/blocks"}}),l?i.querySelector('[data-banner-action="block-editor-shortcode"]')?.remove():G(c,{id:"block-editor-shortcode",icon:"copy",title:"Copy shortcode",onClick:async()=>{const e=Ye(a);E.toast(await N(e)?"Shortcode copied - set the collection slug.":e,{type:"info",duration:5e3})}}),G(c,{id:"block-editor-save",icon:"save",title:"Save (Ctrl+S)",onClick:()=>z()});const be=e=>{!(e.ctrlKey||e.metaKey)||e.key!=="s"||(e.preventDefault(),e.stopImmediatePropagation(),z())};document.addEventListener("keydown",be,!0),r(()=>document.removeEventListener("keydown",be,!0));const fe=()=>{if(C.peek())try{S.set(g(a),{form:B(),base:w.peek(),at:new Date().toISOString()})}catch{}},he=e=>{C.peek()&&(fe(),e.preventDefault(),e.returnValue="")};window.addEventListener("beforeunload",he),r(()=>window.removeEventListener("beforeunload",he)),p={hash:Ee,path:Me,dirty:()=>C.peek(),label:()=>l?"the new block":`"${a}"`,dispose(){A=!0,fe(),f.splice(0).reverse().forEach(e=>{try{e()}catch{}})}},I.scan(s);let J=null;try{J=S.get(g(a))}catch{}const L=Fe(J,w.peek());if(J)try{S.remove?S.remove(g(a)):S.set(g(a),null)}catch{}if(L){const e=L.at?` (${D(L.at).format("D MMM HH:mm")})`:"",t=l?"a new block":`"${Se(a)}"`,v=L.stale?`You left unsaved changes to ${t}${e}, but the block has been saved since. Restore your version anyway?`:`You left unsaved changes to ${t}${e}. Restore them?`;if(await E.confirm(v)&&P()){const d={...u,...L.form};l||(d.name=a),re(d)}}}};
@@ -1,5 +1,64 @@
1
- import{api as B}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"]),te=["public","token","subscriber","editor","manager","admin"],le=["create","read","update","delete"];let x=[],$=null,q=!0,z=null,A="file",X={};function ne(e){return e.toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-|-$/g,"")}function ae(e){return ee.find(t=>t.value===e)?.label||e}function oe(e){const t={...x[e]},a=document.getElementById(`fb-label-${e}`),o=document.getElementById(`fb-name-${e}`),s=document.getElementById(`fb-type-${e}`),g=document.getElementById(`fb-required-${e}`),y=document.getElementById(`fb-placeholder-${e}`),b=document.getElementById(`fb-helper-${e}`);if(a&&(t.label=a.value.trim()||t.label),o&&(t.name=o.value.trim()||t.name),s&&(t.type=s.value||t.type),g&&(t.required=g.checked),y&&(t.placeholder=y.value.trim()),b&&(t.helper=b.value.trim()),Q.has(t.type)){const n=document.getElementById(`fb-options-${e}`);n&&(t.options=n.value.split(`
2
- `).filter(c=>c.trim()).map(c=>{const[h,...w]=c.split(":");return{value:h.trim(),label:w.join(":").trim()||h.trim()}}))}const l=document.getElementById(`fb-span-${e}`);if(document.getElementById(`fb-fullwidth-${e}`)?.checked)t.fullWidth=!0,delete t.span;else{delete t.fullWidth;const n=parseInt(l?.value,10);n>1?t.span=n:delete t.span}return t}function Z(){return x.map((e,t)=>oe(t))}function de(e,t){const a=document.createElement("div");a.className="fb-field-card",a.dataset.index=t,a.style.cssText="border:1px solid var(--dm-border,#333);border-radius:8px;margin-bottom:.75rem;overflow:hidden;";const o=document.createElement("div");o.className="fb-field-header",o.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;",a.draggable=!0,s.addEventListener("mousedown",()=>{a.draggable=!0}),a.addEventListener("dragstart",d=>{z=t,d.dataTransfer.effectAllowed="move",a.style.opacity="0.4"}),a.addEventListener("dragend",()=>{a.style.opacity="",document.querySelectorAll(".fb-field-card").forEach(d=>d.classList.remove("fb-drag-over"))}),a.addEventListener("dragover",d=>{d.preventDefault(),d.dataTransfer.dropEffect="move",document.querySelectorAll(".fb-field-card").forEach(N=>N.classList.remove("fb-drag-over")),a.classList.add("fb-drag-over")}),a.addEventListener("dragleave",()=>{a.classList.remove("fb-drag-over")}),a.addEventListener("drop",d=>{if(d.preventDefault(),a.classList.remove("fb-drag-over"),z===null||z===t)return;x=Z();const[N]=x.splice(z,1);x.splice(t,0,N),z=null,O(document.getElementById("fields-list"))});const g=document.createElement("span");g.className="fb-field-summary",g.style.cssText="flex:1;font-weight:500;font-size:.9rem;",g.textContent=e.label||"(Untitled field)";const y=document.createElement("span");y.style.cssText="font-size:.75rem;opacity:.5;",y.textContent=ae(e.type);const b=document.createElement("span");b.className="fb-field-chevron",b.innerHTML='<span data-icon="chevron-down" data-icon-size="12" style="display:inline-flex;align-items:center;vertical-align:middle;"></span>',b.style.cssText="opacity:.5;transition:transform .2s;display:inline-flex;align-items:center;";const l=document.createElement("button");l.type="button",l.innerHTML='<span data-icon="x" data-icon-size="13" style="display:inline-flex;align-items:center;vertical-align:middle;"></span>',l.className="btn btn-sm btn-ghost",l.style.cssText="padding:.15rem .45rem;line-height:1;opacity:.6;",l.title="Remove field",l.addEventListener("click",d=>{d.stopPropagation(),x.splice(t,1),O(document.getElementById("fields-list"))}),o.appendChild(s),o.appendChild(g),o.appendChild(y),o.appendChild(b),o.appendChild(l),Domma.icons.scan(o);const n=document.createElement("div");n.className="fb-field-body",n.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 h=document.createElement("div"),w=document.createElement("label");w.className="form-label",w.textContent="Label";const i=document.createElement("input");i.id=`fb-label-${t}`,i.type="text",i.className="form-input",i.value=e.label||"",i.addEventListener("input",()=>{g.textContent=i.value.trim()||"(Untitled field)";const d=document.getElementById(`fb-name-${t}`);d&&!d.dataset.manual&&(d.value=ne(i.value).replace(/-/g,"_"))}),h.appendChild(w),h.appendChild(i);const p=document.createElement("div"),u=document.createElement("label");u.className="form-label",u.textContent="Name (key)";const m=document.createElement("input");m.id=`fb-name-${t}`,m.type="text",m.className="form-input",m.value=e.name||"",m.addEventListener("input",()=>{m.dataset.manual="1"}),p.appendChild(u),p.appendChild(m);const C=document.createElement("div"),L=document.createElement("label");L.className="form-label",L.textContent="Type";const v=document.createElement("select");v.id=`fb-type-${t}`,v.className="form-input",ee.forEach(d=>{const N=document.createElement("option");N.value=d.value,N.textContent=d.label,d.value===e.type&&(N.selected=!0),v.appendChild(N)}),v.addEventListener("change",()=>{y.textContent=ae(v.value);const d=n.querySelector(".fb-options-wrap");d&&(d.style.display=Q.has(v.value)?"":"none")}),C.appendChild(L),C.appendChild(v),c.appendChild(h),c.appendChild(p),c.appendChild(C);const f=document.createElement("div");f.style.cssText="display:grid;grid-template-columns:1fr 1fr auto;gap:.6rem;align-items:end;margin-bottom:.6rem;";const r=document.createElement("div"),T=document.createElement("label");T.className="form-label",T.textContent="Placeholder";const I=document.createElement("input");I.id=`fb-placeholder-${t}`,I.type="text",I.className="form-input",I.value=e.placeholder||"",r.appendChild(T),r.appendChild(I);const H=document.createElement("div"),_=document.createElement("label");_.className="form-label",_.textContent="Helper text";const F=document.createElement("input");F.id=`fb-helper-${t}`,F.type="text",F.className="form-input",F.value=e.helper||"",H.appendChild(_),H.appendChild(F);const V=document.createElement("div");V.style.cssText="padding-bottom:.35rem;";const D=document.createElement("label");D.style.cssText="display:flex;align-items:center;gap:.4rem;cursor:pointer;white-space:nowrap;";const M=document.createElement("input");M.id=`fb-required-${t}`,M.type="checkbox",M.checked=!!e.required,D.appendChild(M),D.appendChild(document.createTextNode("Required")),V.appendChild(D),f.appendChild(r),f.appendChild(H),f.appendChild(V);const j=document.createElement("div");j.className="fb-options-wrap",j.style.display=Q.has(e.type)?"":"none";const Y=document.createElement("label");Y.className="form-label",Y.textContent="Options (one per line: value: Label)";const W=document.createElement("textarea");W.id=`fb-options-${t}`,W.className="form-input",W.rows=4,W.value=(e.options||[]).map(d=>typeof d=="string"?`${d}: ${d}`:`${d.value??""}: ${d.label??d.value??""}`).join(`
3
- `),j.appendChild(Y),j.appendChild(W);const k=document.createElement("div");k.className="fb-grid-row",k.style.gridTemplateColumns="1fr auto",k.style.gap=".6rem",k.style.alignItems="end",k.style.marginBottom=".6rem",k.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 S=document.createElement("input");S.id=`fb-span-${t}`,S.type="number",S.className="form-input",S.min="1",S.max="6",S.value=e.span>1?String(e.span):"1",G.appendChild(J),G.appendChild(S);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 P=document.createElement("input");return P.id=`fb-fullwidth-${t}`,P.type="checkbox",P.checked=!!e.fullWidth,U.appendChild(P),U.appendChild(document.createTextNode("Full Width")),K.appendChild(U),k.appendChild(G),k.appendChild(K),n.appendChild(c),n.appendChild(f),n.appendChild(j),n.appendChild(k),o.addEventListener("click",()=>{const d=n.style.display!=="none";n.style.display=d?"none":"",b.style.transform=d?"":"rotate(180deg)"}),a.appendChild(o),a.appendChild(n),a}function O(e){if(e){if(e.textContent="",x.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}x.forEach((t,a)=>{e.appendChild(de(t,a))})}}function ie(e,t,a){t.textContent="",le.forEach(i=>{const p=e?.[i]||{enabled:!1,access:"admin"},u=document.createElement("div");u.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 m=document.createElement("strong");m.textContent=i.charAt(0).toUpperCase()+i.slice(1),m.style.cssText="font-size:.9rem;";const C=document.createElement("label");C.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-${i}-enabled`,L.checked=!!p.enabled,C.appendChild(L),C.appendChild(document.createTextNode("Enable public access"));const v=document.createElement("select");if(v.id=`api-${i}-access`,v.className="form-input",te.forEach(f=>{const r=document.createElement("option");r.value=f,r.textContent=f.charAt(0).toUpperCase()+f.slice(1),f===p.access&&(r.selected=!0),v.appendChild(r)}),u.appendChild(m),u.appendChild(C),u.appendChild(v),t.appendChild(u),i==="read"){const f=document.createElement("div");f.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 r=document.createElement("label");r.htmlFor="api-read-fields",r.style.cssText="font-size:.85rem;color:var(--dm-text-muted,#aaa);",r.textContent="Read fields";const T=document.createElement("input");T.type="text",T.id="api-read-fields",T.className="form-input",T.placeholder="Comma-separated allowlist - empty = all fields",T.value=Array.isArray(p.fields)?p.fields.join(", "):"",f.appendChild(r),f.appendChild(T),t.appendChild(f)}});const o=a||{},s=document.createElement("div");s.style.cssText="display:grid;grid-template-columns:140px 1fr 160px;gap:.75rem;align-items:center;padding:.6rem 0;border-top:2px solid var(--dm-border,#333);";const g=document.createElement("strong");g.textContent="Export",g.style.cssText="font-size:.9rem;";const y=document.createElement("label");y.style.cssText="display:flex;align-items:center;gap:.45rem;cursor:pointer;font-size:.875rem;";const b=document.createElement("input");b.type="checkbox",b.id="export-enabled",b.checked=o.enabled!==!1,y.appendChild(b),y.appendChild(document.createTextNode("Allow export"));const l=document.createElement("select");l.id="export-access",l.className="form-input",te.filter(i=>i!=="token").forEach(i=>{const p=document.createElement("option");p.value=i,p.textContent=i.charAt(0).toUpperCase()+i.slice(1),i===o.access&&(p.selected=!0),l.appendChild(p)}),s.appendChild(g),s.appendChild(y),s.appendChild(l),t.appendChild(s);const n=document.createElement("div");n.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.style.cssText="font-size:.85rem;color:var(--dm-text-muted,#aaa);",c.textContent="Formats";const h=document.createElement("div");h.style.cssText="display:flex;gap:1rem;";const w=Array.isArray(o.formats)?o.formats:["csv","json"];["csv","json"].forEach(i=>{const p=document.createElement("label");p.style.cssText="display:flex;align-items:center;gap:.35rem;cursor:pointer;font-size:.875rem;";const u=document.createElement("input");u.type="checkbox",u.id=`export-format-${i}`,u.checked=w.includes(i),p.appendChild(u),p.appendChild(document.createTextNode(i.toUpperCase())),h.appendChild(p)}),n.appendChild(c),n.appendChild(h),t.appendChild(n)}function ce(e,t){E.dropdown("#storage-adapter-trigger",{items:[{label:"File (default)",value:"file"},{label:"MongoDB",value:"mongodb"}],onSelect:({item:o})=>{e.find("#storage-adapter").val(o.value),e.find("#storage-adapter-label").text(o.label);const s=o.value==="mongodb";e.find("#storage-connection-group").toggle(s),e.find("#storage-migration-warning").toggle(s&&!q)}});const a=t.map(o=>({label:o,value:o}));E.dropdown("#storage-connection-trigger",{items:a.length?a:[{label:"default",value:"default"}],onSelect:({item:o})=>{e.find("#storage-connection").val(o.value),e.find("#storage-connection-label").text(o.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 le.forEach(t=>{const a=document.getElementById(`api-${t}-enabled`)?.checked??!1,o=document.getElementById(`api-${t}-access`)?.value||"admin";if(e[t]={enabled:a,access:o},t==="read"){const s=(document.getElementById("api-read-fields")?.value||"").split(",").map(g=>g.trim()).filter(Boolean);s.length&&(e[t].fields=s)}}),e}function pe(){const e=["csv","json"].filter(t=>document.getElementById(`export-format-${t}`)?.checked);return{enabled:document.getElementById("export-enabled")?.checked??!0,access:document.getElementById("export-access")?.value||"subscriber",formats:e}}export const collectionEditorView={templateUrl:"/admin/js/templates/collection-editor.html",async onMount(e){x=[],$=null,q=!0;const t=window.location.hash.match(/\/collections\/edit\/([^/?#]+)/);t&&($=t[1],q=!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(n=>{n.style.display=l?"grid":"none"})});const a=e.find("#fields-list").get(0),o=e.find("#api-access-rows").get(0);X={};const s=e.find("#collection-project").get(0);if(s)try{(await B.projects.list()).forEach(n=>{const c=document.createElement("option");c.value=n.slug,c.textContent=n.name||n.slug,s.appendChild(c)})}catch{}const g=await B.collections.proStatus();g?.pro&&$!=="roles"&&(e.find("#storage-tab-btn").show(),ce(e,g.connections));let y={create:{enabled:!1,access:"admin"},read:{enabled:!0,access:"public"},update:{enabled:!1,access:"admin"},delete:{enabled:!1,access:"admin"}},b={enabled:!0,access:"subscriber",formats:["csv","json"]};if(q){const l=e.find("#field-title").get(0),n=e.find("#field-slug").get(0);l&&n&&(l.addEventListener("input",()=>{n.dataset.manual||(n.value=ne(l.value))}),n.addEventListener("input",()=>{n.dataset.manual="1"}))}else try{const l=await B.collections.get($);if(!l){E.toast("Collection not found.",{type:"error"}),R.navigate("/collections");return}const n=e.find("#editor-title-text").get(0);n&&(n.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",x=l.fields||[],y=l.api||y,b=l.export||b,A=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"))),$==="roles"&&e.find("#storage-tab-btn").hide()}catch{E.toast("Failed to load collection.",{type:"error"}),R.navigate("/collections");return}O(a),ie(y,o,b),e.find("#add-field-btn").off("click").on("click",()=>{x=Z(),x.push({id:`field-${Date.now()}`,name:"",label:"",type:"string",required:!1,placeholder:"",helper:"",options:[],validation:[],logic:null}),O(a);const l=a.querySelectorAll(".fb-field-card");if(l.length){const n=l[l.length-1],c=n.querySelector(".fb-field-body"),h=n.querySelector(".fb-field-chevron");c&&(c.style.display=""),h&&(h.style.transform="rotate(180deg)"),n.querySelector(`#fb-label-${x.length-1}`)?.focus()}}),e.find("#save-collection-btn").off("click").on("click",async()=>{const l=e.find("#field-title").val().trim(),n=e.find("#field-slug").val().trim(),c=e.find("#field-description").val().trim();if(!l){E.toast("Title is required.",{type:"warning"});return}const h=Z(),w=re(),i=pe(),p=e.find("#collection-layout").val()||"stacked",u=parseInt(e.find("#collection-columns").val(),10)||2,m=se(),C=e.find("#collection-bundled").is(":checked"),L=e.find("#collection-project").val()||"",v={...X||{},project:L||null},f=e.find("#save-collection-btn");f.prop("disabled",!0);try{if(q){const r=await B.collections.create({title:l,slug:n,description:c,layout:p,columns:u,fields:h,api:w,export:i,storage:m,meta:v,...C?{bundled:!0}:{}});$=r.slug,A=m.adapter||"file",q=!1,E.toast("Collection created.",{type:"success"}),R.navigate(`/collections/edit/${r.slug}`)}else if((m.adapter||"file")!==A){let r=0;try{r=(await B.collections.listEntries($,{limit:1}))?.total??0}catch{}const T=A==="file"?`file \u2192 ${m.adapter}`:`${A} \u2192 ${m.adapter||"file"}`;if(r>0&&await E.confirm(`You changed the storage adapter (${T}).
4
-
5
- Migrate ${r} existing ${r===1?"entry":"entries"} to the new storage?`)){const I=await B.collections.migrateStorage($,m);A=m.adapter||"file",E.toast(`Migrated ${I.migrated} of ${I.total} entries.`,{type:"success"})}else await B.collections.update($,{title:l,description:c,layout:p,columns:u,fields:h,api:w,export:i,storage:m,meta:v,...C?{bundled:!0}:{bundled:!1}}),A=m.adapter||"file",E.toast("Collection saved.",{type:"success"})}else await B.collections.update($,{title:l,description:c,layout:p,columns:u,fields:h,api:w,export:i,storage:m,meta:v,...C?{bundled:!0}:{bundled:!1}}),E.toast("Collection saved.",{type:"success"})}catch(r){E.toast(r.message||"Failed to save.",{type:"error"})}finally{f.prop("disabled",!1)}}),Domma.icons.scan()}};
1
+ import{api as C}from"../api.js";import{addBannerAction as ue}from"../lib/plugin-chrome.js";import{helpPopovers as Ae}from"../lib/help-popover.js";import{onceClosed as ot,openMenuAt as Fe}from"../lib/tool-kit.js";import{ACCESS_LEVELS as at,EXPORT_ACCESS as lt,FIELD_TYPES as st,OPTION_TYPES as it,checkForm as rt,draftOf as ct,duplicateField as dt,errorCount as K,fieldFromPanel as Le,fieldProblem as ut,fieldRow as pt,formFromCollection as De,formFromDraft as ft,moveField as Re,nameFromLabel as mt,newField as vt,panelForm as ht,payloadFromForm as bt,slugify as Z,snapshot as H,storageChange as Ne}from"../lib/collection-editor-arrange.js";const gt=/^\/collections\/(new|edit\/[^/?#]+)\/?(\?.*)?$/,yt=s=>`collection-editor.draft.${s||"new"}`,wt=`
2
+ <div class="cex-row" data-key="{{key}}" tabindex="0" draggable="true" data-bind-class="rowCls">
3
+ <span class="cex-grip" title="Drag to reorder" aria-hidden="true">\u283F</span>
4
+ <span class="cex-main"><strong data-bind-text="label"></strong><small data-bind-text="meta"></small><small class="cex-problem" data-bind-hidden="!problem" data-bind-text="problem"></small></span>
5
+ <span class="cex-chip is-ok" data-bind-hidden="!required">Required</span>
6
+ <span class="cex-chip" data-bind-hidden="!hidden">Hidden</span>
7
+ <button type="button" class="cex-kebab" aria-label="Actions for this field" title="Actions">\u22EE</button>
8
+ </div>`,ee=s=>String(s??"").replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;"),xt=s=>s.charAt(0).toUpperCase()+s.slice(1);function kt(s){const c=document.createElement("template");return c.innerHTML=s.trim(),c.content.firstElementChild}function Te(s,c){const i=s.querySelector(".dm-plugin-banner-name");if(!i)return;const $=document.createElement("span");$.className="dm-plugin-banner-parent",$.textContent="Collections";const y=document.createElement("span");y.className="dm-plugin-banner-sep",y.setAttribute("aria-hidden","true"),y.textContent="\u203A",i.replaceChildren($,y,document.createTextNode(c))}let g=null,qe=!1;const te=s=>String(s||"").split("?")[0];function Et(){qe||!window.R?.use||(qe=!0,R.use((s,c,i)=>{if(!(g&&gt.test(c?.path||"")&&te(s?.path)!==te(c?.path))||!g.dirty())return i();const y=`#${c.path}`;return E.confirm("This collection has unsaved changes. Leave anyway? They are kept as a draft for your next visit.").then(r=>{r?i():history.replaceState(null,"",y)})}),M.subscribe("router:afterChange",({to:s}={})=>{if(g&&te(s?.path)!==g.path){const c=g;g=null,c.dispose()}}))}export const collectionEditorView={templateUrl:"/admin/js/templates/collection-editor.html",async onMount(s){if(Et(),g){const e=g;g=null,e.dispose()}const c=s.get(0),i=c.querySelector(".cex")||c,$=te(location.hash.replace(/^#/,""))||"/collections/new",y=$.match(/^\/collections\/edit\/([^/?#]+)/),r=y?decodeURIComponent(y[1]):null,P=!r,pe=[],b=e=>(pe.push(e),e);let fe=!1;const ne=()=>!fe&&i.isConnected,me=E.loader(c,{type:"dots"});let O=null,oe=[],B=null,ve=[],d=0;try{[O,oe,B,ve]=await Promise.all([r?C.collections.get(r):Promise.resolve(null),C.projects.list().catch(()=>[]),C.collections.proStatus().catch(()=>null),P?C.collections.list().then(e=>(e||[]).map(t=>t.slug)).catch(()=>[]):Promise.resolve([])]),r&&(d=(await C.collections.listEntries(r,{limit:1}).catch(()=>null))?.total??0)}catch(e){me.destroy(),E.toast(e?.status===404?"Collection not found.":`Could not load the collection: ${e.message||e}`,{type:"error"}),R.navigate("/collections");return}if(me.destroy(),!i.isConnected)return;if(r&&!O){E.toast("Collection not found.",{type:"error"}),R.navigate("/collections");return}const he=new URLSearchParams(location.hash.split("?")[1]||"").get("project")||"",m=De(O,{project:he}),w=Object.fromEntries(m.fields.map(e=>[e._key,e.name])),Pe=O?.meta||{};let U=m.storageAdapter;const n={};for(const[e,t]of Object.entries(m))n[e]=M.observable(t);const x=M.observable(P),W=M.observable(!1),Y=()=>Object.fromEntries(Object.keys(n).map(e=>[e,n[e].peek()])),be=()=>Object.fromEntries(Object.keys(n).map(e=>[e,n[e].value])),L=M.observable(H(m)),_=M.computed(()=>H(be())!==L.value);let V=!P;b(M.effect(()=>{const e=n.title.value;V||(n.slug.value=Z(e))}));const p=M.computed(()=>rt(be(),{isNew:x.value,taken:ve,original:w})),ge=M.observable([]);b(M.effect(()=>{const e=p.value.fields||{},t=n.layout.value==="grid";ge.value=n.fields.value.map(o=>pt(o,e,{grid:t}))}));const Ie=M.computed(()=>new Set(n.fields.value.map(e=>e.name))),Be={...n,isNew:x,saving:W,dirty:_,rows:ge,errTitle:M.computed(()=>p.value.title||""),errSlug:M.computed(()=>p.value.slug||""),errColumns:M.computed(()=>p.value.columns||""),errExport:M.computed(()=>p.value.export||""),problemCount:M.computed(()=>K(p.value)),problemText:M.computed(()=>{const e=K(p.value);return e?`${e} thing${e===1?"":"s"} to fix before it can be saved`:""}),settingsBad:M.computed(()=>!!(p.value.title||p.value.slug||p.value.columns)),fieldsBad:M.computed(()=>!!p.value.fields),exportBad:M.computed(()=>!!p.value.export),fieldCount:M.computed(()=>n.fields.value.length),slugHint:M.computed(()=>x.value?`Will be created as "${Z(n.slug.value||n.title.value)||"\u2026"}".`:"Cannot be changed once the collection exists."),apiPath:M.computed(()=>`/api/collections/${Z(n.slug.value||n.title.value)||"<slug>"}/public`),unknownReadFields:M.computed(()=>{const e=String(n.readFields.value||"").split(",").map(t=>t.trim()).filter(t=>t&&!Ie.value.has(t));return e.length?`Not a field of this collection: ${e.join(", ")}`:""}),showStorage:M.observable(!!B?.pro&&r!=="roles"),storageNote:M.computed(()=>{const e=Ne(U,n.storageAdapter.value);return!e||x.value?"":d?`Switching ${e}: Save asks whether to move the ${d} existing ${d===1?"entry":"entries"} across. Export them first if they matter.`:`Switching ${e}. There are no entries to move.`})},ye=i.querySelector("#collection-project");for(const e of oe){const t=document.createElement("option");t.value=e.slug,t.textContent=e.name||e.slug,ye.appendChild(t)}if(m.project&&!oe.some(e=>e.slug===m.project)){const e=document.createElement("option");e.value=m.project,e.textContent=`${m.project} (not found)`,ye.appendChild(e)}const _e=(e,t)=>{for(const o of t){const l=document.createElement("option");l.value=o,l.textContent=xt(o),e.appendChild(l)}};i.querySelectorAll(".cex-op select").forEach(e=>_e(e,e.id==="export-access"?lt:at));const Ke=i.querySelector("#storage-connection"),He=Array.isArray(B?.connections)&&B.connections.length?B.connections:["default"];for(const e of new Set([...He,m.storageConnection])){const t=document.createElement("option");t.value=e,t.textContent=e,Ke.appendChild(t)}const N=e=>n.fields.peek().findIndex(t=>t._key===e),j=e=>{n.fields.value=e};function z(e,t){const o=N(e);o<0||(j(Re(n.fields.peek(),o,o+t)),requestAnimationFrame(()=>i.querySelector(`.cex-row[data-key="${e}"]`)?.focus()))}async function ae(e){const t=N(e);if(t<0)return;const o=n.fields.peek()[t];!x.peek()&&d&&w[e]&&!await E.confirm(`Remove the field "${ee(o.label||o.name)}"? The ${d} existing ${d===1?"entry keeps its":"entries keep their"} value for it, but it is no longer shown or edited. Nothing is lost until you Save - Discard puts it back.`)||j(n.fields.peek().filter(l=>l._key!==e))}function G(e,{isAdded:t=!1}={}){const o=N(e);if(o<0)return;const l=n.fields.peek()[o],v=ht(l),f={};for(const[a,k]of Object.entries(v))f[a]=M.observable(k);const u=JSON.stringify(v),X=()=>Object.fromEntries(Object.keys(f).map(a=>[a,f[a].peek()])),Ce=()=>JSON.stringify(X())!==u;let Me=!!l.name;const Je=M.effect(()=>{const a=f.label.value;Me||(f.name.value=mt(a))}),Xe=new Set(n.fields.peek().filter(a=>a._key!==e).map(a=>a.name)),$e=M.computed(()=>{const a=Le(l,Object.fromEntries(Object.keys(f).map(k=>[k,f[k].value])));return a.name&&Xe.has(a.name)?`"${a.name}" is used by another field.`:ut(a,w[e])}),Qe=M.computed(()=>!x.peek()&&d&&w[e]&&f.name.value.trim()!==w[e]?`Entries keep their value under "${w[e]}" - renaming the field does not move it.`:""),Ze={...f,problem:$e,renamed:Qe,isOptions:M.computed(()=>it.has(f.type.value)),isGrid:M.computed(()=>n.layout.value==="grid")},h=E.slideover({title:t?"New field":`Field: ${ee(l.label||l.name||"Untitled")}`,size:"md",position:"right"}),F=kt(`
9
+ <div class="cex-panel">
10
+ <div class="cex-grid">
11
+ <div>
12
+ <label class="form-label" for="cex-f-label">Label</label>
13
+ <input id="cex-f-label" type="text" class="form-input" maxlength="120" placeholder="e.g. Price" data-model="label.value">
14
+ </div>
15
+ <div>
16
+ <label class="form-label" for="cex-f-name">Name
17
+ <span data-help-title="Field name" data-help="What each entry stores this field's value under - in the API, in exports and in block templates. Made from the label until you type one. Letters, digits, _ and -."></span></label>
18
+ <input id="cex-f-name" type="text" class="form-input font-mono" maxlength="80" data-model="name.value">
19
+ </div>
20
+ <div>
21
+ <label class="form-label" for="cex-f-type">Type</label>
22
+ <select id="cex-f-type" class="form-input" data-model="type.value">
23
+ ${st.map(a=>`<option value="${a.value}">${a.label}</option>`).join("")}
24
+ </select>
25
+ </div>
26
+ <div class="cex-setting">
27
+ <label class="form-label" for="cex-f-required">Required
28
+ <span data-help="An entry cannot be saved without it - in the admin and through the API."></span></label>
29
+ <input id="cex-f-required" type="checkbox" class="form-switch" data-model="required.value">
30
+ </div>
31
+ <div>
32
+ <label class="form-label" for="cex-f-ph">Placeholder</label>
33
+ <input id="cex-f-ph" type="text" class="form-input" maxlength="200" data-model="placeholder.value">
34
+ </div>
35
+ <div>
36
+ <label class="form-label" for="cex-f-help">Helper text
37
+ <span data-help="Shown under the field in the entry form."></span></label>
38
+ <input id="cex-f-help" type="text" class="form-input" maxlength="300" data-model="helper.value">
39
+ </div>
40
+ <div class="cex-wide" data-bind-hidden="!isOptions.value">
41
+ <label class="form-label" for="cex-f-options">Options
42
+ <span data-help-title="Options" data-help="One per line, as value: Label - the value is what is stored, the label what is shown. A line with no colon uses the same text for both."></span></label>
43
+ <textarea id="cex-f-options" class="form-input font-mono" rows="5" placeholder="s: Small&#10;m: Medium" data-model="options.value"></textarea>
44
+ </div>
45
+ <div data-bind-hidden="!isGrid.value">
46
+ <label class="form-label" for="cex-f-span">Column span
47
+ <span data-help="How many of the grid's columns the field takes, in the entry form."></span></label>
48
+ <input id="cex-f-span" type="number" class="form-input" min="1" max="6" data-model="span.value" data-bind-disabled="fullWidth.value">
49
+ </div>
50
+ <div class="cex-setting" data-bind-hidden="!isGrid.value">
51
+ <label class="form-label" for="cex-f-full">Full width</label>
52
+ <input id="cex-f-full" type="checkbox" class="form-switch" data-model="fullWidth.value">
53
+ </div>
54
+ </div>
55
+ <small class="cex-err" data-bind-hidden="!problem.value" data-bind-text="problem.value"></small>
56
+ <p class="cex-warn" data-bind-hidden="!renamed.value"><span data-bind-text="renamed.value"></span></p>
57
+ <div class="cex-panel-actions">
58
+ <button type="button" class="btn btn-sm btn-ghost text-danger" data-on-click="remove"><span data-icon="trash" data-icon-size="13"></span> Remove</button>
59
+ <small class="cex-hint">Ctrl+Enter - Done. Save the collection to keep it.</small>
60
+ <span class="cex-spacer"></span>
61
+ <button type="button" class="btn btn-sm btn-ghost" data-on-click="cancel">Cancel</button>
62
+ <button type="button" class="btn btn-sm btn-primary" data-on-click="done" data-bind-disabled="problem.value"><span data-icon="check" data-icon-size="13"></span> Done</button>
63
+ </div>
64
+ </div>`);let Q=!1,ce=!1;const et=h.close.bind(h);function Oe(){if($e.peek())return;const a=Le(l,X());j(n.fields.peek().map(k=>k._key===e?a:k)),Q=!0,h.close()}h.element.appendChild(F);const tt=Ae(F),nt=M.applyBindings(Ze,F,{methods:{done:Oe,cancel:()=>h.close(),remove:async()=>{Q=!0,h.close(),await ae(e)}}}),je=F.querySelector("#cex-f-name");je.addEventListener("input",()=>{Me=!!je.value}),F.addEventListener("keydown",a=>{(a.ctrlKey||a.metaKey)&&a.key==="Enter"&&(a.preventDefault(),Oe())}),ot(h,()=>{Je(),nt.dispose(),tt(),ie.delete(h)}),h.close=async a=>{if(!Q&&a!==!0&&Ce()){if(ce)return;ce=!0;const de=await E.confirm("Discard the changes to this field?");if(ce=!1,!de)return}Q=!0;const k=et();return t&&!Ce()&&!l.name&&j(n.fields.peek().filter(de=>de._key!==e)),k},ie.add(h),I.scan(F),h.open(),F.querySelector("#cex-f-label")?.focus()}function Ue(){const e=vt();j([...n.fields.peek(),e]),Ve.activate?.(1),G(e._key,{isAdded:!0})}function We(e){const t=N(e);if(t<0)return[];const o=n.fields.peek()[t],l=n.fields.peek().length;return[{header:o.label||o.name||"Field"},{label:"Edit\u2026",icon:"edit",action:()=>G(e)},{label:"Move up",icon:"arrow-up",disabled:t===0,action:()=>z(e,-1)},{label:"Move down",icon:"arrow-down",disabled:t===l-1,action:()=>z(e,1)},{label:"Duplicate",icon:"copy",action:()=>j(dt(n.fields.peek(),t))},{type:"divider"},{label:"Remove\u2026",icon:"trash",danger:!0,action:()=>ae(e)}]}async function le(){if(W.peek())return;const e=p.peek();if(K(e)){E.toast(`Not saved - ${K(e)} thing${K(e)===1?"":"s"} to fix first (marked on the tabs).`,{type:"warning"});return}const t=bt(Y(),{isNew:x.peek(),meta:Pe});W.value=!0;try{if(x.peek()){const l=await C.collections.create(t);L.value=H(Y()),re(),E.toast(`Created "${t.title}".`,{type:"success"}),R.navigate(`/collections/edit/${encodeURIComponent(l.slug)}`);return}const o=Ne(U,t.storage.adapter);if(o&&d){if(await E.confirm(`You changed the storage (${o}). Move the ${d} existing ${d===1?"entry":"entries"} to the new storage?`)){const v=await C.collections.migrateStorage(r,t.storage);E.toast(`Moved ${v.migrated} of ${v.total} entries.`,{type:"success"})}else if(!await E.confirm(`Switch without moving them? The ${d} ${d===1?"entry stays":"entries stay"} in ${ee(U)} storage and this collection will show none until they are moved.`))return}if(await C.collections.update(r,t),!ne())return;U=t.storage.adapter,o&&(d=(await C.collections.listEntries(r,{limit:1}).catch(()=>null))?.total??d);for(const l of n.fields.peek())w[l._key]=l.name;L.value=H(Y()),re(),Te(c,t.title),E.toast("Collection saved.",{type:"success"})}catch(o){E.toast(`Not saved: ${o.message||o}`,{type:"error"})}finally{W.value=!1}}async function Ye(){if(!_.peek()||!await E.confirm("Discard the changes you have not saved?"))return;const e=De(O,{project:he});for(const t of e.fields)w[t._key]=t.name;for(const[t,o]of Object.entries(e))n[t].value=o;L.value=H(e),V=!x.peek()}const T=i.querySelector(".cex-list");T.innerHTML=wt;const Ve=E.tabs(i.querySelector(".cex-tabs"),{tabSelector:".tab-item",panelSelector:".tab-panel"}),ze=M.applyBindings(Be,i,{methods:{save:()=>le(),discard:()=>Ye(),addField:()=>Ue()}});b(()=>ze.dispose()),b(Ae(i,{position:"bottom"}));const se=i.querySelector("#field-slug"),we=()=>{V=!!se.value};se.addEventListener("input",we),b(()=>se.removeEventListener("input",we));const ie=new Set;b(()=>[...ie].forEach(e=>{try{e.close(!0)}catch{}}));const Ge=E.contextMenu(T,{cursor:"glyph",match:".cex-row",inherit:!1,density:"compact",items:e=>We(e.dataset.key)});b(()=>Ge?.destroy?.());let q=null;const xe={click:e=>{const t=e.target.closest(".cex-row");if(!t)return;const o=e.target.closest(".cex-kebab");if(o){Fe(o);return}G(t.dataset.key)},keydown:e=>{const t=e.target.closest(".cex-row");!t||e.target!==t||(e.key==="F10"&&e.shiftKey?(e.preventDefault(),Fe(t)):e.key==="Enter"?G(t.dataset.key):e.altKey&&e.key==="ArrowUp"?(e.preventDefault(),z(t.dataset.key,-1)):e.altKey&&e.key==="ArrowDown"?(e.preventDefault(),z(t.dataset.key,1)):e.key==="Delete"&&(e.preventDefault(),ae(t.dataset.key)))},dragstart:e=>{const t=e.target.closest(".cex-row");t&&(q=t.dataset.key,t.classList.add("is-dragging"),e.dataTransfer.effectAllowed="move")},dragover:e=>{const t=e.target.closest(".cex-row");!t||!q||(e.preventDefault(),T.querySelectorAll(".is-drag-over").forEach(o=>o.classList.remove("is-drag-over")),t.classList.add("is-drag-over"))},drop:e=>{const t=e.target.closest(".cex-row");if(!t||!q)return;e.preventDefault();const o=N(q),l=N(t.dataset.key);q=null,j(Re(n.fields.peek(),o,l))},dragend:()=>{q=null,T.querySelectorAll(".is-drag-over, .is-dragging").forEach(e=>e.classList.remove("is-drag-over","is-dragging"))}};for(const[e,t]of Object.entries(xe))T.addEventListener(e,t);b(()=>{for(const[e,t]of Object.entries(xe))T.removeEventListener(e,t)}),Te(c,O?.title||"New collection"),ue(s,{id:"collection-editor-back",icon:"arrow-left",title:"All collections",onClick:()=>R.navigate("/collections")}),r&&ue(s,{id:"collection-editor-entries",icon:"list",title:"Open entries",onClick:()=>R.navigate(`/collections/${encodeURIComponent(r)}/entries`)}),ue(s,{id:"collection-editor-save",icon:"save",title:"Save (Ctrl+S)",onClick:()=>le()});const ke=e=>{!(e.ctrlKey||e.metaKey)||e.key!=="s"||!ne()||(e.preventDefault(),e.stopImmediatePropagation(),le())};document.addEventListener("keydown",ke,!0),b(()=>document.removeEventListener("keydown",ke,!0));const J=yt(r);function re(){try{S.remove?S.remove(J):S.set(J,null)}catch{}}const Ee=()=>{if(_.peek())try{S.set(J,{form:ct(Y()),base:L.peek(),at:new Date().toISOString()})}catch{}},Se=e=>{_.peek()&&(Ee(),e.preventDefault(),e.returnValue="")};window.addEventListener("beforeunload",Se),b(()=>window.removeEventListener("beforeunload",Se)),g={path:$,dirty:()=>_.peek(),dispose(){fe=!0,Ee(),pe.splice(0).reverse().forEach(e=>{try{e()}catch{}})}},I.scan(i);let A=null;try{A=S.get(J)}catch{}if(A?.form){re();const e=A.base&&A.base!==L.peek(),t=A.at?` (${D(A.at).format("D MMM HH:mm")})`:"",o=r?`changes to "${ee(O?.title||r)}"`:"a new collection",l=e?`You left unsaved ${o}${t}, but the saved collection has changed since. Restore your version anyway?`:`You left unsaved ${o}${t}. Restore them?`;if(await E.confirm(l)&&ne()){const v=ft(A.form,m);P||(v.slug=m.slug);const f=Object.fromEntries(m.fields.filter(u=>u.id).map(u=>[u.id,u.name]));for(const u of v.fields)u.id&&f[u.id]!==void 0&&(w[u._key]=f[u.id]);for(const[u,X]of Object.entries(v))n[u]&&(n[u].value=X);P&&(V=!!v.slug&&v.slug!==Z(v.title))}}}};