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.
- package/CLAUDE.md +1 -1
- package/admin/js/api.js +1 -1
- package/admin/js/app.js +2 -2
- package/admin/js/lib/action-editor-arrange.js +1 -0
- package/admin/js/lib/api-endpoint-editor-arrange.js +2 -0
- package/admin/js/lib/block-editor-arrange.js +2 -0
- package/admin/js/lib/collection-editor-arrange.js +3 -0
- package/admin/js/lib/collection-entries-arrange.js +1 -0
- package/admin/js/lib/component-editor-arrange.js +27 -0
- package/admin/js/lib/context-menu-editor-arrange.js +1 -0
- package/admin/js/lib/dashboard-arrange.js +1 -0
- package/admin/js/lib/form-editor-arrange.js +1 -0
- package/admin/js/lib/form-submissions-arrange.js +2 -0
- package/admin/js/lib/menu-editor-arrange.js +2 -0
- package/admin/js/lib/page-editor-arrange.js +1 -0
- package/admin/js/lib/plugin-code-arrange.js +1 -0
- package/admin/js/lib/project-page-arrange.js +1 -0
- package/admin/js/lib/role-editor-arrange.js +3 -0
- package/admin/js/lib/user-editor-arrange.js +1 -0
- package/admin/js/lib/view-editor-arrange.js +14 -0
- package/admin/js/templates/action-editor.html +223 -147
- package/admin/js/templates/api-endpoint-editor.html +172 -117
- package/admin/js/templates/block-editor.html +194 -203
- package/admin/js/templates/collection-editor.html +191 -113
- package/admin/js/templates/collection-entries.html +99 -36
- package/admin/js/templates/component-editor.html +128 -126
- package/admin/js/templates/context-menu-editor.html +208 -148
- package/admin/js/templates/dashboard.html +184 -22
- package/admin/js/templates/form-editor.html +41 -26
- package/admin/js/templates/form-submissions.html +78 -26
- package/admin/js/templates/menu-editor.html +57 -17
- package/admin/js/templates/menu-locations.html +54 -11
- package/admin/js/templates/page-editor.html +47 -26
- package/admin/js/templates/plugin-code.html +91 -73
- package/admin/js/templates/project-detail.html +83 -145
- package/admin/js/templates/project-settings.html +88 -53
- package/admin/js/templates/role-editor.html +158 -83
- package/admin/js/templates/user-editor.html +150 -14
- package/admin/js/templates/view-editor.html +203 -180
- package/admin/js/templates/view-preview.html +45 -45
- package/admin/js/views/action-editor.js +7 -1
- package/admin/js/views/api-endpoint-editor.js +2 -3
- package/admin/js/views/block-editor.js +14 -8
- package/admin/js/views/collection-editor.js +64 -5
- package/admin/js/views/collection-entries.js +50 -1
- package/admin/js/views/component-editor.js +2 -28
- package/admin/js/views/context-menu-editor.js +54 -55
- package/admin/js/views/dashboard.js +7 -1
- package/admin/js/views/form-editor.js +7 -7
- package/admin/js/views/form-submissions.js +11 -1
- package/admin/js/views/index.js +1 -1
- package/admin/js/views/login.js +2 -2
- package/admin/js/views/menu-editor.js +21 -43
- package/admin/js/views/menu-locations.js +16 -1
- package/admin/js/views/page-editor.js +26 -26
- package/admin/js/views/plugin-code.js +23 -4
- package/admin/js/views/project-detail.js +9 -1
- package/admin/js/views/project-settings.js +1 -1
- package/admin/js/views/role-editor.js +11 -3
- package/admin/js/views/user-editor.js +1 -1
- package/admin/js/views/view-editor.js +1 -1
- package/admin/js/views/view-preview.js +1 -1
- package/package.json +1 -1
- package/server/routes/api/dashboard.js +32 -4
- package/server/routes/api/forms.js +104 -13
- package/server/routes/api/users.js +12 -2
- package/server/routes/api/views.js +55 -1
- package/server/services/components.js +9 -0
- package/server/services/users.js +2 -2
- package/server/services/views.js +14 -0
- package/admin/js/templates/dashboard/activity-feed.html +0 -3
- package/admin/js/templates/dashboard/cache.html +0 -32
- package/admin/js/templates/dashboard/health-detail.html +0 -2
- package/admin/js/templates/dashboard/journeys.html +0 -17
- package/admin/js/templates/dashboard/kpi-strip.html +0 -34
- package/admin/js/templates/dashboard/spike-feed.html +0 -3
- package/admin/js/templates/dashboard/top-pages.html +0 -3
- package/admin/js/templates/dashboard/traffic-chart.html +0 -3
- package/admin/js/views/block-editor-enhance.js +0 -1
- package/admin/js/views/dashboard/lib/escape.js +0 -1
- package/admin/js/views/dashboard/widgets/activity-feed.js +0 -1
- package/admin/js/views/dashboard/widgets/cache.js +0 -1
- package/admin/js/views/dashboard/widgets/health-detail.js +0 -1
- package/admin/js/views/dashboard/widgets/journeys.js +0 -1
- package/admin/js/views/dashboard/widgets/kpi-strip.js +0 -1
- package/admin/js/views/dashboard/widgets/spike-feed.js +0 -6
- package/admin/js/views/dashboard/widgets/top-pages.js +0 -1
- 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,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""");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,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""")}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-
|
|
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,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""")}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
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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
|
|
2
|
-
|
|
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
|
-
|
|
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,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,"""),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&>.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 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))}}}};
|