domma-cms 0.36.13 → 0.36.14

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.
@@ -1 +1 @@
1
- import{api as Ee}from"../api.js";function Ae(){const o={"rotate-cw":{viewBox:"0 0 24 24",stroke:"currentColor",fill:"none",strokeWidth:2,path:"M21 2v6h-6M21 8A9 9 0 1 0 19.07 14.4"},"rotate-ccw":{viewBox:"0 0 24 24",stroke:"currentColor",fill:"none",strokeWidth:2,path:"M3 2v6h6M3 8a9 9 0 1 1 1.93 6.4"},"flip-h":{viewBox:"0 0 24 24",stroke:"currentColor",fill:"none",strokeWidth:2,paths:["M12 3v18","M4 6l4 6-4 6","M20 6l-4 6 4 6"]},"flip-v":{viewBox:"0 0 24 24",stroke:"currentColor",fill:"none",strokeWidth:2,paths:["M3 12h18","M6 4l6 4 6-4","M6 20l6-4 6 4"]},lock:{viewBox:"0 0 24 24",stroke:"currentColor",fill:"none",strokeWidth:2,paths:["M17 11H7a2 2 0 0 0-2 2v7a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2v-7a2 2 0 0 0-2-2z","M12 8a4 4 0 0 0-4 4"],path:"M12 8V5a4 4 0 0 1 4 4v2"},unlock:{viewBox:"0 0 24 24",stroke:"currentColor",fill:"none",strokeWidth:2,paths:["M17 11H7a2 2 0 0 0-2 2v7a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2v-7a2 2 0 0 0-2-2z","M8 11V7a4 4 0 0 1 8 0"]},"crop-icon":{viewBox:"0 0 24 24",stroke:"currentColor",fill:"none",strokeWidth:2,paths:["M6 2v14a2 2 0 0 0 2 2h14","M18 22V8a2 2 0 0 0-2-2H2"]},sliders:{viewBox:"0 0 24 24",stroke:"currentColor",fill:"none",strokeWidth:2,paths:["M4 21v-7","M4 10V3","M12 21v-9","M12 8V3","M20 21v-5","M20 12V3","M1 14h6","M9 8h6","M17 16h6"]},droplet:{viewBox:"0 0 24 24",stroke:"currentColor",fill:"none",strokeWidth:2,path:"M12 2.69l5.66 5.66a8 8 0 1 1-11.31 0z"},"image-plus":{viewBox:"0 0 24 24",stroke:"currentColor",fill:"none",strokeWidth:2,paths:["M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h7","M16 5h6","M19 2v6","M3 15l5-5 4 4 3-3 4 4"]},frame:{viewBox:"0 0 24 24",stroke:"currentColor",fill:"none",strokeWidth:2,paths:["M7 3H3v4","M17 3h4v4","M7 21H3v-4","M17 21h4v-4"]},"file-type":{viewBox:"0 0 24 24",stroke:"currentColor",fill:"none",strokeWidth:2,paths:["M14.5 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7.5L14.5 2z","M14 2v6h6","M10 13l-2 4h8l-2-4"]}};for(const[m,M]of Object.entries(o))try{I.register(m,M)}catch{}}function oe(o){const m=document.createElement("span");return m.setAttribute("data-icon",o),m}function K(o,m,M){const i=document.createElement("button");return i.className=o,i.type="button",i.title=M,i.appendChild(oe(m)),i}function je(){const o=document.createElement("span");return o.className="image-editor-sep",o}function Ve(o){for(;o.firstChild;)o.removeChild(o.firstChild)}export async function openImageEditor(o){Ae();let m;try{m=await Ee.media.info(o.name)}catch(M){return E.toast(`Cannot open editor: ${M.message}`,{type:"error"}),!1}return new Promise(M=>{let i=null,F=!1,j=0,D=!1,q=!1,O=!0;const ye=m.width/m.height;let se=null,y=null;const r={brightness:1,contrast:1,saturation:1,blur:0,sharpen:0,gamma:1},u={image:null,position:"bottom-right",opacity:50,scale:15},L={width:0,colour:"#000000",mode:"solid"},v={type:null,quality:85},k=document.createElement("div");k.className="image-editor";const C=document.createElement("div");C.className="image-editor-toolbar";const le=K("btn btn-sm btn-ghost editor-toolbar-btn","crop-icon","Toggle crop"),ke=K("btn btn-sm btn-ghost editor-toolbar-btn","rotate-cw","Rotate clockwise"),xe=K("btn btn-sm btn-ghost editor-toolbar-btn","rotate-ccw","Rotate counter-clockwise"),re=K("btn btn-sm btn-ghost editor-toolbar-btn","flip-h","Flip horizontal"),ie=K("btn btn-sm btn-ghost editor-toolbar-btn","flip-v","Flip vertical");C.appendChild(le),C.appendChild(je()),C.appendChild(xe),C.appendChild(ke),C.appendChild(je()),C.appendChild(re),C.appendChild(ie);const U=document.createElement("div");U.className="image-editor-canvas";const $=document.createElement("img");$.src=o.url+"?t="+Date.now(),$.alt=o.name,$.style.maxWidth="100%",U.appendChild($);const x=document.createElement("div");x.className="image-editor-resize";const Ne=document.createElement("label");Ne.textContent="W";const g=document.createElement("input");g.type="number",g.className="form-input",g.value=m.width,g.min=1;const h=document.createElement("button");h.type="button",h.className="btn btn-sm btn-ghost editor-toolbar-btn active",h.title="Lock aspect ratio",h.appendChild(oe("lock"));const Me=document.createElement("label");Me.textContent="H";const w=document.createElement("input");w.type="number",w.className="form-input",w.value=m.height,w.min=1;const X=document.createElement("span");X.className="text-muted",X.style.marginLeft="auto",X.textContent=`Original: ${m.width}\xD7${m.height}`,x.appendChild(Ne),x.appendChild(g),x.appendChild(h),x.appendChild(Me),x.appendChild(w),x.appendChild(X);const Y=document.createElement("div");Y.className="image-editor-effects";const ce=document.createElement("div");ce.className="image-editor-tab-bar";const de=[{id:"filters",label:"Filters",icon:"droplet"},{id:"adjust",label:"Adjust",icon:"sliders"},{id:"watermark",label:"Watermark",icon:"image-plus"},{id:"border",label:"Border",icon:"frame"},{id:"format",label:"Format",icon:"file-type"}],c={},Le={};de.forEach(({id:e,label:t,icon:a},s)=>{const n=document.createElement("button");n.type="button",n.className="image-editor-tab-btn"+(s===0?" active":""),n.dataset.tab=e,n.appendChild(oe(a));const p=document.createElement("span");p.textContent=t,n.appendChild(p),n.addEventListener("click",()=>Oe(e)),ce.appendChild(n),Le[e]=n;const l=document.createElement("div");l.className="image-editor-tab-panel",l.style.display=s===0?"block":"none",c[e]=l});function Oe(e){de.forEach(({id:t})=>{c[t].style.display=t===e?"block":"none",Le[t].classList.toggle("active",t===e)})}Y.appendChild(ce),de.forEach(({id:e})=>Y.appendChild(c[e]));const Be=[{id:"grayscale",label:"Grayscale",cssPreview:!0},{id:"sepia",label:"Sepia",cssPreview:!0},{id:"warm",label:"Warm",cssPreview:!1},{id:"cool",label:"Cool",cssPreview:!1},{id:"enhance",label:"Enhance",cssPreview:!1},{id:"sharpen",label:"Sharpen",cssPreview:!1},{id:"soften",label:"Soften",cssPreview:!1},{id:"invert",label:"Invert",cssPreview:!0}],me=document.createElement("div");me.className="ie-presets-grid";const Pe={};Be.forEach(({id:e,label:t})=>{const a=document.createElement("button");a.type="button",a.className="btn btn-sm btn-ghost ie-preset-btn",a.textContent=t,a.addEventListener("click",()=>{y=y===e?null:e,Be.forEach(({id:s})=>{Pe[s].classList.toggle("active",s===y)}),ae()}),Pe[e]=a,me.appendChild(a)});const pe=document.createElement("p");pe.className="ie-server-note",pe.textContent="Warm, Cool, Enhance, Sharpen, and Soften have no live preview \u2014 applied on save only.",c.filters.appendChild(me),c.filters.appendChild(pe);const Se={};function A(e,t,a,s,n,p){const l=document.createElement("div");l.className="ie-slider-row";const b=document.createElement("label");b.className="ie-slider-label",b.textContent=e;const d=document.createElement("input");d.type="range",d.min=t,d.max=a,d.step=s,d.value=n,d.className="ie-slider";const f=document.createElement("span");return f.className="ie-slider-val",f.textContent=n,d.addEventListener("input",()=>{const R=parseFloat(d.value);f.textContent=s<1?R.toFixed(1):String(Math.round(R)),p(R)}),l.appendChild(b),l.appendChild(d),l.appendChild(f),{row:l,slider:d,valDisplay:f,defaultVal:n,reset(){d.value=n,f.textContent=n}}}const We=[{key:"brightness",label:"Brightness",min:.2,max:3,step:.1,def:1},{key:"contrast",label:"Contrast",min:.2,max:3,step:.1,def:1},{key:"saturation",label:"Saturation",min:0,max:3,step:.1,def:1},{key:"blur",label:"Blur",min:0,max:10,step:.5,def:0},{key:"sharpen",label:"Sharpen",min:0,max:10,step:.5,def:0},{key:"gamma",label:"Gamma",min:.5,max:3,step:.1,def:1}];We.forEach(({key:e,label:t,min:a,max:s,step:n,def:p})=>{const l=A(t,a,s,n,p,b=>{r[e]=b,ae()});Se[e]=l,c.adjust.appendChild(l.row)});const z=document.createElement("button");z.type="button",z.className="btn btn-sm btn-ghost",z.style.marginTop="0.5rem",z.textContent="Reset adjustments",z.addEventListener("click",()=>{We.forEach(({key:e,def:t})=>{r[e]=t,Se[e].reset()}),ae()}),c.adjust.appendChild(z);const B=document.createElement("button");B.type="button",B.className="btn btn-sm btn-secondary",B.textContent="Choose watermark image\u2026";const N=document.createElement("div");N.className="ie-wm-preview-row",N.style.display="none";const ue=document.createElement("img");ue.className="ie-wm-preview-img";const he=document.createElement("span");he.className="ie-wm-preview-name";const V=document.createElement("button");V.type="button",V.className="btn btn-sm btn-ghost",V.textContent="Remove",V.addEventListener("click",()=>{u.image=null,N.style.display="none",B.style.display=""}),N.appendChild(ue),N.appendChild(he),N.appendChild(V),B.addEventListener("click",async()=>{const e=await De();e&&(u.image=e.name,ue.src=e.url,he.textContent=e.name,N.style.display="flex",B.style.display="none")});const _=document.createElement("div");_.className="ie-slider-row";const be=document.createElement("label");be.className="ie-slider-label",be.textContent="Position";const G=document.createElement("select");G.className="form-select ie-select",["top-left","top-center","top-right","center-left","center","center-right","bottom-left","bottom-center","bottom-right"].forEach(e=>{const t=document.createElement("option");t.value=e,t.textContent=e.replace(/-/g," ").replace(/\b\w/g,a=>a.toUpperCase()),e==="bottom-right"&&(t.selected=!0),G.appendChild(t)}),G.addEventListener("change",()=>{u.position=G.value}),_.appendChild(be),_.appendChild(G);const $e=A("Opacity %",0,100,1,50,e=>{u.opacity=e}),ze=A("Scale %",5,50,1,15,e=>{u.scale=e});c.watermark.appendChild(B),c.watermark.appendChild(N),c.watermark.appendChild(_),c.watermark.appendChild($e.row),c.watermark.appendChild(ze.row);const He=A("Width (px)",0,200,1,0,e=>{L.width=e}),Z=document.createElement("div");Z.className="ie-slider-row";const fe=document.createElement("label");fe.className="ie-slider-label",fe.textContent="Colour";const H=document.createElement("input");H.type="color",H.value="#000000",H.className="ie-colour-input",H.addEventListener("input",()=>{L.colour=H.value}),Z.appendChild(fe),Z.appendChild(H);const ee=document.createElement("div");ee.className="ie-slider-row";const ve=document.createElement("label");ve.className="ie-slider-label",ve.textContent="Mode";const T=document.createElement("select");T.className="form-select ie-select",[["solid","Solid"],["mirror","Mirror"],["repeat","Repeat"]].forEach(([e,t])=>{const a=document.createElement("option");a.value=e,a.textContent=t,T.appendChild(a)}),T.addEventListener("change",()=>{L.mode=T.value}),ee.appendChild(ve),ee.appendChild(T),c.border.appendChild(He.row),c.border.appendChild(Z),c.border.appendChild(ee);const te=document.createElement("div");te.className="ie-slider-row";const Ce=document.createElement("label");Ce.className="ie-slider-label",Ce.textContent="Output format";const P=document.createElement("select");P.className="form-select ie-select",[["","Keep original"],["jpeg","JPEG"],["png","PNG"],["webp","WebP"]].forEach(([e,t])=>{const a=document.createElement("option");a.value=e,a.textContent=t,P.appendChild(a)}),te.appendChild(Ce),te.appendChild(P);const ge=A("Quality",1,100,1,85,e=>{v.quality=e});ge.row.style.display="none",P.addEventListener("change",()=>{v.type=P.value||null,ge.row.style.display=P.value==="jpeg"||P.value==="webp"?"flex":"none"}),c.format.appendChild(te),c.format.appendChild(ge.row);const ne=document.createElement("div");ne.className="image-editor-footer";const Q=document.createElement("button");Q.type="button",Q.className="btn btn-ghost",Q.textContent="Cancel";const J=document.createElement("div");J.style.display="flex",J.style.gap="0.5rem";const S=document.createElement("button");S.type="button",S.className="btn btn-secondary",S.textContent="Save as copy";const W=document.createElement("button");W.type="button",W.className="btn btn-primary",W.textContent="Overwrite",J.appendChild(S),J.appendChild(W),ne.appendChild(Q),ne.appendChild(J),k.appendChild(C),k.appendChild(U),k.appendChild(x),k.appendChild(Y),k.appendChild(ne);const we=E.modal({title:`Edit: ${o.name}`,size:"lg"});we.element.appendChild(k),we.open(),$.addEventListener("ready",()=>{se=U.querySelector(".cropper-canvas"),ae()},{once:!0}),requestAnimationFrame(()=>{i=new Cropper($,{viewMode:1,autoCrop:!1,zoomable:!1,movable:!0}),Domma.icons.scan(k)}),le.addEventListener("click",()=>{F=!F,F?i.crop():i.clear(),le.classList.toggle("active",F)}),ke.addEventListener("click",()=>{i.rotate(90),j=(j+90)%360}),xe.addEventListener("click",()=>{i.rotate(-90),j=(j-90+360)%360}),re.addEventListener("click",()=>{D=!D,i.scaleX(D?-1:1),re.classList.toggle("active",D)}),ie.addEventListener("click",()=>{q=!q,i.scaleY(q?-1:1),ie.classList.toggle("active",q)}),h.addEventListener("click",()=>{O=!O,h.classList.toggle("active",O),Ve(h),h.appendChild(oe(O?"lock":"unlock")),Domma.icons.scan(h)}),g.addEventListener("input",()=>{if(!O)return;const e=parseInt(g.value,10);e>0&&(w.value=Math.round(e/ye))}),w.addEventListener("input",()=>{if(!O)return;const e=parseInt(w.value,10);e>0&&(g.value=Math.round(e*ye))});function Fe(){const e=[];return y==="grayscale"&&e.push("grayscale(1)"),y==="sepia"&&e.push("sepia(1)"),y==="invert"&&e.push("invert(1)"),r.brightness!==1&&e.push(`brightness(${r.brightness})`),r.contrast!==1&&e.push(`contrast(${r.contrast})`),r.saturation!==1&&e.push(`saturate(${r.saturation})`),r.blur>0&&e.push(`blur(${r.blur}px)`),e.join(" ")}function ae(){se&&(se.style.filter=Fe())}async function De(){return new Promise(async e=>{let t;try{t=await Ee.media.list()}catch(l){E.toast(`Failed to load media: ${l.message}`,{type:"error"}),e(null);return}const a=t.filter(l=>/\.(jpg|jpeg|png|webp|gif)$/i.test(l.name)),s=document.createElement("div");if(s.className="ie-media-picker",a.length===0){const l=document.createElement("p");l.className="text-muted",l.style.padding="1rem 0",l.textContent="No images in media library.",s.appendChild(l)}else{const l=document.createElement("div");l.className="ie-media-picker-grid",a.forEach(b=>{const d=document.createElement("div");d.className="ie-media-thumb";const f=document.createElement("img");f.src=b.url,f.alt=b.name;const R=document.createElement("span");R.className="ie-media-thumb-name",R.textContent=b.name,d.appendChild(f),d.appendChild(R),d.addEventListener("click",()=>{p.close(),e(b)}),l.appendChild(d)}),s.appendChild(l)}const n=document.createElement("button");n.type="button",n.className="btn btn-sm btn-ghost",n.style.marginTop="0.75rem",n.textContent="Cancel",n.addEventListener("click",()=>{p.close(),e(null)}),s.appendChild(n);const p=E.modal({title:"Choose watermark image",size:"md"});p.element.appendChild(s),p.open()})}function qe(){const e={};if(j&&(e.rotate=j),q&&(e.flip=!0),D&&(e.flop=!0),F){const n=i.getData(!0);n.width>0&&n.height>0&&(e.crop={left:n.x,top:n.y,width:n.width,height:n.height})}const t=parseInt(g.value,10)||null,a=parseInt(w.value,10)||null;(t!==m.width||a!==m.height)&&(e.resize={width:t,height:a}),y&&(e.preset=y);const s={};return r.brightness!==1&&(s.brightness=r.brightness),r.contrast!==1&&(s.contrast=r.contrast),r.saturation!==1&&(s.saturation=r.saturation),r.blur>0&&(s.blur=r.blur),r.sharpen>0&&(s.sharpen=r.sharpen),r.gamma!==1&&(s.gamma=r.gamma),Object.keys(s).length>0&&(e.adjustments=s),u.image&&(e.watermark={image:u.image,position:u.position,opacity:u.opacity/100,scale:u.scale/100}),L.width>0&&(e.border={width:L.width,colour:L.colour,mode:L.mode}),v.type&&(e.format={type:v.type,quality:v.quality}),e}async function Re(e){W.disabled=!0,S.disabled=!0;try{const t=qe();let a=e;if(!e&&v.type){const s=o.name.substring(o.name.lastIndexOf(".")).toLowerCase(),n="."+v.type;s!==n&&(a=o.name.slice(0,o.name.lastIndexOf("."))+n,t._deleteOriginal=o.name)}await Ee.media.transform(o.name,{operations:t,saveAs:a||void 0}),E.toast("Image saved.",{type:"success"}),Ie(!0)}catch(t){E.toast(`Save failed: ${t.message}`,{type:"error"}),W.disabled=!1,S.disabled=!1}}W.addEventListener("click",async()=>{await E.confirm(`Overwrite "${o.name}"? This cannot be undone.`)&&await Re(null)}),S.addEventListener("click",async()=>{const e=v.type?"."+v.type:o.name.substring(o.name.lastIndexOf(".")),t=o.name.slice(0,o.name.lastIndexOf("."));await Re(`${t}-edited${e}`)}),Q.addEventListener("click",()=>Ie(!1));function Ie(e){i&&(i.destroy(),i=null),we.close(),M(e)}})}
1
+ import{api as Ee}from"../api.js";function Ae(){const o={"rotate-cw":{viewBox:"0 0 24 24",stroke:"currentColor",fill:"none",strokeWidth:2,path:"M21 2v6h-6M21 8A9 9 0 1 0 19.07 14.4"},"rotate-ccw":{viewBox:"0 0 24 24",stroke:"currentColor",fill:"none",strokeWidth:2,path:"M3 2v6h6M3 8a9 9 0 1 1 1.93 6.4"},"flip-h":{viewBox:"0 0 24 24",stroke:"currentColor",fill:"none",strokeWidth:2,paths:["M12 3v18","M4 6l4 6-4 6","M20 6l-4 6 4 6"]},"flip-v":{viewBox:"0 0 24 24",stroke:"currentColor",fill:"none",strokeWidth:2,paths:["M3 12h18","M6 4l6 4 6-4","M6 20l6-4 6 4"]},lock:{viewBox:"0 0 24 24",stroke:"currentColor",fill:"none",strokeWidth:2,paths:["M17 11H7a2 2 0 0 0-2 2v7a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2v-7a2 2 0 0 0-2-2z","M12 8a4 4 0 0 0-4 4"],path:"M12 8V5a4 4 0 0 1 4 4v2"},unlock:{viewBox:"0 0 24 24",stroke:"currentColor",fill:"none",strokeWidth:2,paths:["M17 11H7a2 2 0 0 0-2 2v7a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2v-7a2 2 0 0 0-2-2z","M8 11V7a4 4 0 0 1 8 0"]},"crop-icon":{viewBox:"0 0 24 24",stroke:"currentColor",fill:"none",strokeWidth:2,paths:["M6 2v14a2 2 0 0 0 2 2h14","M18 22V8a2 2 0 0 0-2-2H2"]},sliders:{viewBox:"0 0 24 24",stroke:"currentColor",fill:"none",strokeWidth:2,paths:["M4 21v-7","M4 10V3","M12 21v-9","M12 8V3","M20 21v-5","M20 12V3","M1 14h6","M9 8h6","M17 16h6"]},droplet:{viewBox:"0 0 24 24",stroke:"currentColor",fill:"none",strokeWidth:2,path:"M12 2.69l5.66 5.66a8 8 0 1 1-11.31 0z"},"image-plus":{viewBox:"0 0 24 24",stroke:"currentColor",fill:"none",strokeWidth:2,paths:["M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h7","M16 5h6","M19 2v6","M3 15l5-5 4 4 3-3 4 4"]},frame:{viewBox:"0 0 24 24",stroke:"currentColor",fill:"none",strokeWidth:2,paths:["M7 3H3v4","M17 3h4v4","M7 21H3v-4","M17 21h4v-4"]},"file-type":{viewBox:"0 0 24 24",stroke:"currentColor",fill:"none",strokeWidth:2,paths:["M14.5 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7.5L14.5 2z","M14 2v6h6","M10 13l-2 4h8l-2-4"]}};for(const[p,M]of Object.entries(o))try{I.register(p,M)}catch{}}function oe(o){const p=document.createElement("span");return p.setAttribute("data-icon",o),p}function K(o,p,M){const i=document.createElement("button");return i.className=o,i.type="button",i.title=M,i.appendChild(oe(p)),i}function je(){const o=document.createElement("span");return o.className="image-editor-sep",o}function Ve(o){for(;o.firstChild;)o.removeChild(o.firstChild)}export async function openImageEditor(o){Ae();let p;try{p=await Ee.media.info(o.name)}catch(M){return E.toast(`Cannot open editor: ${M.message}`,{type:"error"}),!1}return new Promise(M=>{let i=null,F=!1,j=0,D=!1,q=!1,O=!0;const ye=p.width/p.height;let se=null,y=null;const r={brightness:1,contrast:1,saturation:1,blur:0,sharpen:0,gamma:1},u={image:null,position:"bottom-right",opacity:50,scale:15},L={width:0,colour:"#000000",mode:"solid"},v={type:null,quality:85},k=document.createElement("div");k.className="image-editor";const C=document.createElement("div");C.className="image-editor-toolbar";const le=K("btn btn-sm btn-ghost editor-toolbar-btn","crop-icon","Toggle crop"),ke=K("btn btn-sm btn-ghost editor-toolbar-btn","rotate-cw","Rotate clockwise"),xe=K("btn btn-sm btn-ghost editor-toolbar-btn","rotate-ccw","Rotate counter-clockwise"),re=K("btn btn-sm btn-ghost editor-toolbar-btn","flip-h","Flip horizontal"),ie=K("btn btn-sm btn-ghost editor-toolbar-btn","flip-v","Flip vertical");C.appendChild(le),C.appendChild(je()),C.appendChild(xe),C.appendChild(ke),C.appendChild(je()),C.appendChild(re),C.appendChild(ie);const U=document.createElement("div");U.className="image-editor-canvas";const $=document.createElement("img");$.src=o.url+"?t="+Date.now(),$.alt=o.name,$.style.maxWidth="100%",U.appendChild($);const x=document.createElement("div");x.className="image-editor-resize";const Ne=document.createElement("label");Ne.textContent="W";const g=document.createElement("input");g.type="number",g.className="form-input",g.value=p.width,g.min=1;const h=document.createElement("button");h.type="button",h.className="btn btn-sm btn-ghost editor-toolbar-btn active",h.title="Lock aspect ratio",h.appendChild(oe("lock"));const Me=document.createElement("label");Me.textContent="H";const w=document.createElement("input");w.type="number",w.className="form-input",w.value=p.height,w.min=1;const X=document.createElement("span");X.className="text-muted",X.style.marginLeft="auto",X.textContent=`Original: ${p.width}\xD7${p.height}`,x.appendChild(Ne),x.appendChild(g),x.appendChild(h),x.appendChild(Me),x.appendChild(w),x.appendChild(X);const Y=document.createElement("div");Y.className="image-editor-effects";const ce=document.createElement("div");ce.className="image-editor-tab-bar";const de=[{id:"filters",label:"Filters",icon:"droplet"},{id:"adjust",label:"Adjust",icon:"sliders"},{id:"watermark",label:"Watermark",icon:"image-plus"},{id:"border",label:"Border",icon:"frame"},{id:"format",label:"Format",icon:"file-type"}],c={},Le={};de.forEach(({id:e,label:t,icon:a},s)=>{const n=document.createElement("button");n.type="button",n.className="image-editor-tab-btn"+(s===0?" active":""),n.dataset.tab=e,n.appendChild(oe(a));const m=document.createElement("span");m.textContent=t,n.appendChild(m),n.addEventListener("click",()=>Oe(e)),ce.appendChild(n),Le[e]=n;const l=document.createElement("div");l.className="image-editor-tab-panel",l.style.display=s===0?"block":"none",c[e]=l});function Oe(e){de.forEach(({id:t})=>{c[t].style.display=t===e?"block":"none",Le[t].classList.toggle("active",t===e)})}Y.appendChild(ce),de.forEach(({id:e})=>Y.appendChild(c[e]));const Be=[{id:"grayscale",label:"Grayscale",cssPreview:!0},{id:"sepia",label:"Sepia",cssPreview:!0},{id:"warm",label:"Warm",cssPreview:!1},{id:"cool",label:"Cool",cssPreview:!1},{id:"enhance",label:"Enhance",cssPreview:!1},{id:"sharpen",label:"Sharpen",cssPreview:!1},{id:"soften",label:"Soften",cssPreview:!1},{id:"invert",label:"Invert",cssPreview:!0}],pe=document.createElement("div");pe.className="ie-presets-grid";const Pe={};Be.forEach(({id:e,label:t})=>{const a=document.createElement("button");a.type="button",a.className="btn btn-sm btn-ghost ie-preset-btn",a.textContent=t,a.addEventListener("click",()=>{y=y===e?null:e,Be.forEach(({id:s})=>{Pe[s].classList.toggle("active",s===y)}),ae()}),Pe[e]=a,pe.appendChild(a)});const me=document.createElement("p");me.className="ie-server-note",me.textContent="Warm, Cool, Enhance, Sharpen, and Soften have no live preview \u2014 applied on save only.",c.filters.appendChild(pe),c.filters.appendChild(me);const Se={};function A(e,t,a,s,n,m){const l=document.createElement("div");l.className="ie-slider-row";const b=document.createElement("label");b.className="ie-slider-label",b.textContent=e;const d=document.createElement("input");d.type="range",d.min=t,d.max=a,d.step=s,d.value=n,d.className="ie-slider";const f=document.createElement("span");return f.className="ie-slider-val",f.textContent=n,d.addEventListener("input",()=>{const R=parseFloat(d.value);f.textContent=s<1?R.toFixed(1):String(Math.round(R)),m(R)}),l.appendChild(b),l.appendChild(d),l.appendChild(f),{row:l,slider:d,valDisplay:f,defaultVal:n,reset(){d.value=n,f.textContent=n}}}const We=[{key:"brightness",label:"Brightness",min:.2,max:3,step:.1,def:1},{key:"contrast",label:"Contrast",min:.2,max:3,step:.1,def:1},{key:"saturation",label:"Saturation",min:0,max:3,step:.1,def:1},{key:"blur",label:"Blur",min:0,max:10,step:.5,def:0},{key:"sharpen",label:"Sharpen",min:0,max:10,step:.5,def:0},{key:"gamma",label:"Gamma",min:.5,max:3,step:.1,def:1}];We.forEach(({key:e,label:t,min:a,max:s,step:n,def:m})=>{const l=A(t,a,s,n,m,b=>{r[e]=b,ae()});Se[e]=l,c.adjust.appendChild(l.row)});const z=document.createElement("button");z.type="button",z.className="btn btn-sm btn-ghost",z.style.marginTop="0.5rem",z.textContent="Reset adjustments",z.addEventListener("click",()=>{We.forEach(({key:e,def:t})=>{r[e]=t,Se[e].reset()}),ae()}),c.adjust.appendChild(z);const B=document.createElement("button");B.type="button",B.className="btn btn-sm btn-secondary",B.textContent="Choose watermark image\u2026";const N=document.createElement("div");N.className="ie-wm-preview-row",N.style.display="none";const ue=document.createElement("img");ue.className="ie-wm-preview-img";const he=document.createElement("span");he.className="ie-wm-preview-name";const V=document.createElement("button");V.type="button",V.className="btn btn-sm btn-ghost",V.textContent="Remove",V.addEventListener("click",()=>{u.image=null,N.style.display="none",B.style.display=""}),N.appendChild(ue),N.appendChild(he),N.appendChild(V),B.addEventListener("click",async()=>{const e=await De();e&&(u.image=e.name,ue.src=e.url,he.textContent=e.name,N.style.display="flex",B.style.display="none")});const _=document.createElement("div");_.className="ie-slider-row";const be=document.createElement("label");be.className="ie-slider-label",be.textContent="Position";const G=document.createElement("select");G.className="form-select ie-select",["top-left","top-center","top-right","center-left","center","center-right","bottom-left","bottom-center","bottom-right"].forEach(e=>{const t=document.createElement("option");t.value=e,t.textContent=e.replace(/-/g," ").replace(/\b\w/g,a=>a.toUpperCase()),e==="bottom-right"&&(t.selected=!0),G.appendChild(t)}),G.addEventListener("change",()=>{u.position=G.value}),_.appendChild(be),_.appendChild(G);const $e=A("Opacity %",0,100,1,50,e=>{u.opacity=e}),ze=A("Scale %",5,50,1,15,e=>{u.scale=e});c.watermark.appendChild(B),c.watermark.appendChild(N),c.watermark.appendChild(_),c.watermark.appendChild($e.row),c.watermark.appendChild(ze.row);const He=A("Width (px)",0,200,1,0,e=>{L.width=e}),Z=document.createElement("div");Z.className="ie-slider-row";const fe=document.createElement("label");fe.className="ie-slider-label",fe.textContent="Colour";const H=document.createElement("input");H.type="color",H.value="#000000",H.className="ie-colour-input",H.addEventListener("input",()=>{L.colour=H.value}),Z.appendChild(fe),Z.appendChild(H);const ee=document.createElement("div");ee.className="ie-slider-row";const ve=document.createElement("label");ve.className="ie-slider-label",ve.textContent="Mode";const T=document.createElement("select");T.className="form-select ie-select",[["solid","Solid"],["mirror","Mirror"],["repeat","Repeat"]].forEach(([e,t])=>{const a=document.createElement("option");a.value=e,a.textContent=t,T.appendChild(a)}),T.addEventListener("change",()=>{L.mode=T.value}),ee.appendChild(ve),ee.appendChild(T),c.border.appendChild(He.row),c.border.appendChild(Z),c.border.appendChild(ee);const te=document.createElement("div");te.className="ie-slider-row";const Ce=document.createElement("label");Ce.className="ie-slider-label",Ce.textContent="Output format";const P=document.createElement("select");P.className="form-select ie-select",[["","Keep original"],["jpeg","JPEG"],["png","PNG"],["webp","WebP"]].forEach(([e,t])=>{const a=document.createElement("option");a.value=e,a.textContent=t,P.appendChild(a)}),te.appendChild(Ce),te.appendChild(P);const ge=A("Quality",1,100,1,85,e=>{v.quality=e});ge.row.style.display="none",P.addEventListener("change",()=>{v.type=P.value||null,ge.row.style.display=P.value==="jpeg"||P.value==="webp"?"flex":"none"}),c.format.appendChild(te),c.format.appendChild(ge.row);const ne=document.createElement("div");ne.className="image-editor-footer";const Q=document.createElement("button");Q.type="button",Q.className="btn btn-ghost",Q.textContent="Cancel";const J=document.createElement("div");J.style.display="flex",J.style.gap="0.5rem";const S=document.createElement("button");S.type="button",S.className="btn btn-secondary",S.textContent="Save as copy";const W=document.createElement("button");W.type="button",W.className="btn btn-primary",W.textContent="Overwrite",J.appendChild(S),J.appendChild(W),ne.appendChild(Q),ne.appendChild(J),k.appendChild(C),k.appendChild(U),k.appendChild(x),k.appendChild(Y),k.appendChild(ne);const we=E.slideover({title:`Edit: ${o.name}`,size:"lg",position:"right"});we.element.appendChild(k),we.open(),$.addEventListener("ready",()=>{se=U.querySelector(".cropper-canvas"),ae()},{once:!0}),requestAnimationFrame(()=>{i=new Cropper($,{viewMode:1,autoCrop:!1,zoomable:!1,movable:!0}),Domma.icons.scan(k)}),le.addEventListener("click",()=>{F=!F,F?i.crop():i.clear(),le.classList.toggle("active",F)}),ke.addEventListener("click",()=>{i.rotate(90),j=(j+90)%360}),xe.addEventListener("click",()=>{i.rotate(-90),j=(j-90+360)%360}),re.addEventListener("click",()=>{D=!D,i.scaleX(D?-1:1),re.classList.toggle("active",D)}),ie.addEventListener("click",()=>{q=!q,i.scaleY(q?-1:1),ie.classList.toggle("active",q)}),h.addEventListener("click",()=>{O=!O,h.classList.toggle("active",O),Ve(h),h.appendChild(oe(O?"lock":"unlock")),Domma.icons.scan(h)}),g.addEventListener("input",()=>{if(!O)return;const e=parseInt(g.value,10);e>0&&(w.value=Math.round(e/ye))}),w.addEventListener("input",()=>{if(!O)return;const e=parseInt(w.value,10);e>0&&(g.value=Math.round(e*ye))});function Fe(){const e=[];return y==="grayscale"&&e.push("grayscale(1)"),y==="sepia"&&e.push("sepia(1)"),y==="invert"&&e.push("invert(1)"),r.brightness!==1&&e.push(`brightness(${r.brightness})`),r.contrast!==1&&e.push(`contrast(${r.contrast})`),r.saturation!==1&&e.push(`saturate(${r.saturation})`),r.blur>0&&e.push(`blur(${r.blur}px)`),e.join(" ")}function ae(){se&&(se.style.filter=Fe())}async function De(){return new Promise(async e=>{let t;try{t=await Ee.media.list()}catch(l){E.toast(`Failed to load media: ${l.message}`,{type:"error"}),e(null);return}const a=t.filter(l=>/\.(jpg|jpeg|png|webp|gif)$/i.test(l.name)),s=document.createElement("div");if(s.className="ie-media-picker",a.length===0){const l=document.createElement("p");l.className="text-muted",l.style.padding="1rem 0",l.textContent="No images in media library.",s.appendChild(l)}else{const l=document.createElement("div");l.className="ie-media-picker-grid",a.forEach(b=>{const d=document.createElement("div");d.className="ie-media-thumb";const f=document.createElement("img");f.src=b.url,f.alt=b.name;const R=document.createElement("span");R.className="ie-media-thumb-name",R.textContent=b.name,d.appendChild(f),d.appendChild(R),d.addEventListener("click",()=>{m.close(),e(b)}),l.appendChild(d)}),s.appendChild(l)}const n=document.createElement("button");n.type="button",n.className="btn btn-sm btn-ghost",n.style.marginTop="0.75rem",n.textContent="Cancel",n.addEventListener("click",()=>{m.close(),e(null)}),s.appendChild(n);const m=E.slideover({title:"Choose watermark image",size:"md",position:"right"});m.element.appendChild(s),m.open()})}function qe(){const e={};if(j&&(e.rotate=j),q&&(e.flip=!0),D&&(e.flop=!0),F){const n=i.getData(!0);n.width>0&&n.height>0&&(e.crop={left:n.x,top:n.y,width:n.width,height:n.height})}const t=parseInt(g.value,10)||null,a=parseInt(w.value,10)||null;(t!==p.width||a!==p.height)&&(e.resize={width:t,height:a}),y&&(e.preset=y);const s={};return r.brightness!==1&&(s.brightness=r.brightness),r.contrast!==1&&(s.contrast=r.contrast),r.saturation!==1&&(s.saturation=r.saturation),r.blur>0&&(s.blur=r.blur),r.sharpen>0&&(s.sharpen=r.sharpen),r.gamma!==1&&(s.gamma=r.gamma),Object.keys(s).length>0&&(e.adjustments=s),u.image&&(e.watermark={image:u.image,position:u.position,opacity:u.opacity/100,scale:u.scale/100}),L.width>0&&(e.border={width:L.width,colour:L.colour,mode:L.mode}),v.type&&(e.format={type:v.type,quality:v.quality}),e}async function Re(e){W.disabled=!0,S.disabled=!0;try{const t=qe();let a=e;if(!e&&v.type){const s=o.name.substring(o.name.lastIndexOf(".")).toLowerCase(),n="."+v.type;s!==n&&(a=o.name.slice(0,o.name.lastIndexOf("."))+n,t._deleteOriginal=o.name)}await Ee.media.transform(o.name,{operations:t,saveAs:a||void 0}),E.toast("Image saved.",{type:"success"}),Ie(!0)}catch(t){E.toast(`Save failed: ${t.message}`,{type:"error"}),W.disabled=!1,S.disabled=!1}}W.addEventListener("click",async()=>{await E.confirm(`Overwrite "${o.name}"? This cannot be undone.`)&&await Re(null)}),S.addEventListener("click",async()=>{const e=v.type?"."+v.type:o.name.substring(o.name.lastIndexOf(".")),t=o.name.slice(0,o.name.lastIndexOf("."));await Re(`${t}-edited${e}`)}),Q.addEventListener("click",()=>Ie(!1));function Ie(e){i&&(i.destroy(),i=null),we.close(),M(e)}})}
@@ -1,8 +1,8 @@
1
- import{api as m}from"../api.js";import{keepTableIconsAlive as f}from"../lib/table-icons.js";function d(a){return String(a??"").replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")}function g(a){return!Array.isArray(a)||a.length===0?'<span class="text-muted">All collections in project</span>':d(a.map(l=>{const r=Array.isArray(l.verbs)&&l.verbs.length?l.verbs.join(","):"all";return`${l.collection}: ${r}`}).join("; "))}function h(a){const l=[];for(const r of(a||"").split(`
2
- `)){const n=r.trim();if(!n)continue;const[o,e]=n.split(":").map(s=>s.trim());if(!o)return{error:`Invalid scope line: "${n}"`};const t={collection:o};if(e){const s=e.split(",").map(c=>c.trim()).filter(Boolean),i=s.find(c=>!["create","read","update","delete"].includes(c));if(i)return{error:`Unknown verb "${i}" in scope "${n}"`};t.verbs=s}l.push(t)}return{scopes:l}}function b(a,l,r){const n=document.createElement("div");n.style.marginBottom=".75rem";const o=document.createElement("label");if(o.className="form-label",o.textContent=a,n.appendChild(o),n.appendChild(l),r){const e=document.createElement("p");e.className="text-muted",e.style.cssText="font-size:.8rem;margin:.25rem 0 0;",e.textContent=r,n.appendChild(e)}return n}function v(a,l){const r=E.modal({title:"Token created",size:"sm"}),n=document.createElement("div");n.style.padding="1rem";const o=document.createElement("p");o.innerHTML="<strong>Copy this token now.</strong> It is shown once and cannot be recovered \u2014 only a hash is stored.",o.style.marginBottom=".75rem",n.appendChild(o);const e=document.createElement("code");e.textContent=a,e.style.cssText="display:block;padding:.6rem;word-break:break-all;user-select:all;margin-bottom:.75rem;",n.appendChild(e);const t=document.createElement("button");t.className="btn btn-primary",t.textContent="Copy to clipboard",t.style.marginRight=".5rem",t.addEventListener("click",async()=>{try{await navigator.clipboard.writeText(a),t.textContent="Copied!"}catch{E.toast("Copy failed \u2014 select the token text manually.",{type:"error"})}}),n.appendChild(t);const s=document.createElement("button");s.className="btn btn-secondary",s.textContent="Done",s.addEventListener("click",()=>{r.close(),l()}),n.appendChild(s),r.element.appendChild(n),r.open()}function x(a,l){const r=E.modal({title:"New API Token",size:"md"}),n=document.createElement("div");n.style.padding=".5rem 1rem 1rem";const o=document.createElement("input");o.type="text",o.className="form-input",o.placeholder="e.g. mobile-app, ci-pipeline";const e=document.createElement("select");e.className="form-input";for(const c of a){const p=document.createElement("option");p.value=c.slug,p.textContent=`${c.name} (${c.slug})`,e.appendChild(p)}const t=document.createElement("input");t.type="datetime-local",t.className="form-input";const s=document.createElement("textarea");s.className="form-input",s.rows=3,s.placeholder=`jobs: read
3
- enquiries: create, read`,n.appendChild(b("Name",o)),n.appendChild(b("Project",e,"The token only works on collections in this project.")),n.appendChild(b("Expires (optional)",t,"Leave empty for a non-expiring token.")),n.appendChild(b("Scopes (optional)",s,'One per line: "collection: verb, verb". Leave empty to allow every collection in the project.'));const i=document.createElement("button");i.className="btn btn-primary",i.textContent="Create token",i.addEventListener("click",async()=>{const c=o.value.trim();if(!c){E.toast("A token name is required.",{type:"error"});return}const{scopes:p,error:y}=h(s.value);if(y){E.toast(y,{type:"error"});return}const k={name:c,project:e.value,scopes:p,expiresAt:t.value?new Date(t.value).toISOString():null};try{i.disabled=!0;const{plaintext:u}=await m.apiTokens.create(k);r.close(),v(u,l)}catch(u){i.disabled=!1,E.toast(`Create failed: ${u.message||u}`,{type:"error"})}}),n.appendChild(i),r.element.appendChild(n),r.open(),setTimeout(()=>o.focus(),100)}export const apiTokensView={templateUrl:"/admin/js/templates/api-tokens.html",async onMount(a){const l=E.loader(a.get(0),{type:"dots"}),[r,n]=await Promise.all([m.apiTokens.list().catch(()=>[]),m.projects.list().catch(()=>[])]);l.destroy(),T.create("#api-tokens-table",{data:r,emptyMessage:'No API tokens yet. Click "New token" to create one.',columns:[{key:"name",title:"Name",sortable:!0},{key:"project",title:"Project",sortable:!0,render:e=>`<code>${d(e)}</code>`},{key:"tokenHint",title:"Token",render:e=>`<code>dcms_\u2026${d(e||"????")}</code>`},{key:"scopes",title:"Scopes",render:e=>g(e)},{key:"enabled",title:"Status",render:(e,t)=>t.expiresAt&&Date.parse(t.expiresAt)<Date.now()?'<span class="badge badge-warning">Expired</span>':e?'<span class="badge badge-success">Enabled</span>':'<span class="badge badge-secondary">Disabled</span>'},{key:"expiresAt",title:"Expires",render:e=>e?D(e).format("D MMM YYYY, HH:mm"):'<span class="text-muted">\u2014</span>'},{key:"lastUsedAt",title:"Last used",render:e=>e?D(e).format("D MMM YYYY, HH:mm"):'<span class="text-muted">Never</span>'},{key:"_actions",title:"Actions",render:(e,t)=>`
1
+ import{api as m}from"../api.js";import{keepTableIconsAlive as g}from"../lib/table-icons.js";function d(a){return String(a??"").replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")}function f(a){return!Array.isArray(a)||a.length===0?'<span class="text-muted">All collections in project</span>':d(a.map(l=>{const r=Array.isArray(l.verbs)&&l.verbs.length?l.verbs.join(","):"all";return`${l.collection}: ${r}`}).join("; "))}function h(a){const l=[];for(const r of(a||"").split(`
2
+ `)){const n=r.trim();if(!n)continue;const[o,e]=n.split(":").map(s=>s.trim());if(!o)return{error:`Invalid scope line: "${n}"`};const t={collection:o};if(e){const s=e.split(",").map(c=>c.trim()).filter(Boolean),i=s.find(c=>!["create","read","update","delete"].includes(c));if(i)return{error:`Unknown verb "${i}" in scope "${n}"`};t.verbs=s}l.push(t)}return{scopes:l}}function b(a,l,r){const n=document.createElement("div");n.style.marginBottom=".75rem";const o=document.createElement("label");if(o.className="form-label",o.textContent=a,n.appendChild(o),n.appendChild(l),r){const e=document.createElement("p");e.className="text-muted",e.style.cssText="font-size:.8rem;margin:.25rem 0 0;",e.textContent=r,n.appendChild(e)}return n}function v(a,l){const r=E.slideover({title:"Token created",size:"sm",position:"right"}),n=document.createElement("div");n.style.padding="1rem";const o=document.createElement("p");o.innerHTML="<strong>Copy this token now.</strong> It is shown once and cannot be recovered \u2014 only a hash is stored.",o.style.marginBottom=".75rem",n.appendChild(o);const e=document.createElement("code");e.textContent=a,e.style.cssText="display:block;padding:.6rem;word-break:break-all;user-select:all;margin-bottom:.75rem;",n.appendChild(e);const t=document.createElement("button");t.className="btn btn-primary",t.textContent="Copy to clipboard",t.style.marginRight=".5rem",t.addEventListener("click",async()=>{try{await navigator.clipboard.writeText(a),t.textContent="Copied!"}catch{E.toast("Copy failed \u2014 select the token text manually.",{type:"error"})}}),n.appendChild(t);const s=document.createElement("button");s.className="btn btn-secondary",s.textContent="Done",s.addEventListener("click",()=>{r.close(),l()}),n.appendChild(s),r.element.appendChild(n),r.open()}function x(a,l){const r=E.slideover({title:"New API Token",size:"md",position:"right"}),n=document.createElement("div");n.style.padding=".5rem 1rem 1rem";const o=document.createElement("input");o.type="text",o.className="form-input",o.placeholder="e.g. mobile-app, ci-pipeline";const e=document.createElement("select");e.className="form-input";for(const c of a){const p=document.createElement("option");p.value=c.slug,p.textContent=`${c.name} (${c.slug})`,e.appendChild(p)}const t=document.createElement("input");t.type="datetime-local",t.className="form-input";const s=document.createElement("textarea");s.className="form-input",s.rows=3,s.placeholder=`jobs: read
3
+ enquiries: create, read`,n.appendChild(b("Name",o)),n.appendChild(b("Project",e,"The token only works on collections in this project.")),n.appendChild(b("Expires (optional)",t,"Leave empty for a non-expiring token.")),n.appendChild(b("Scopes (optional)",s,'One per line: "collection: verb, verb". Leave empty to allow every collection in the project.'));const i=document.createElement("button");i.className="btn btn-primary",i.textContent="Create token",i.addEventListener("click",async()=>{const c=o.value.trim();if(!c){E.toast("A token name is required.",{type:"error"});return}const{scopes:p,error:y}=h(s.value);if(y){E.toast(y,{type:"error"});return}const k={name:c,project:e.value,scopes:p,expiresAt:t.value?new Date(t.value).toISOString():null};try{i.disabled=!0;const{plaintext:u}=await m.apiTokens.create(k);r.close(),v(u,l)}catch(u){i.disabled=!1,E.toast(`Create failed: ${u.message||u}`,{type:"error"})}}),n.appendChild(i),r.element.appendChild(n),r.open(),setTimeout(()=>o.focus(),100)}export const apiTokensView={templateUrl:"/admin/js/templates/api-tokens.html",async onMount(a){const l=E.loader(a.get(0),{type:"dots"}),[r,n]=await Promise.all([m.apiTokens.list().catch(()=>[]),m.projects.list().catch(()=>[])]);l.destroy(),T.create("#api-tokens-table",{data:r,emptyMessage:'No API tokens yet. Click "New token" to create one.',columns:[{key:"name",title:"Name",sortable:!0},{key:"project",title:"Project",sortable:!0,render:e=>`<code>${d(e)}</code>`},{key:"tokenHint",title:"Token",render:e=>`<code>dcms_\u2026${d(e||"????")}</code>`},{key:"scopes",title:"Scopes",render:e=>f(e)},{key:"enabled",title:"Status",render:(e,t)=>t.expiresAt&&Date.parse(t.expiresAt)<Date.now()?'<span class="badge badge-warning">Expired</span>':e?'<span class="badge badge-success">Enabled</span>':'<span class="badge badge-secondary">Disabled</span>'},{key:"expiresAt",title:"Expires",render:e=>e?D(e).format("D MMM YYYY, HH:mm"):'<span class="text-muted">\u2014</span>'},{key:"lastUsedAt",title:"Last used",render:e=>e?D(e).format("D MMM YYYY, HH:mm"):'<span class="text-muted">Never</span>'},{key:"_actions",title:"Actions",render:(e,t)=>`
4
4
  <span style="display:inline-flex;gap:0.25rem;">
5
5
  <button class="btn btn-sm btn-ghost btn-toggle-token" data-id="${d(t.id)}" data-enabled="${t.enabled?"1":""}" data-tooltip="${t.enabled?"Disable":"Enable"}"><span data-icon="${t.enabled?"pause":"play"}" data-icon-size="14"></span></button>
6
6
  <button class="btn btn-sm btn-ghost btn-revoke-token" data-id="${d(t.id)}" data-name="${d(t.name)}" data-tooltip="Revoke"><span data-icon="trash" data-icon-size="14"></span></button>
7
7
  </span>
8
- `}]}),Domma.icons.scan(a.get(0)),f(a.get(0)),document.querySelectorAll("#api-tokens-table [data-tooltip]").forEach(e=>{E.tooltip(e,{content:e.getAttribute("data-tooltip"),position:"top"})});const o=()=>location.reload();a.off("click","#btn-new-token").on("click","#btn-new-token",()=>{if(!n.length){E.toast("No projects available \u2014 create a project first.",{type:"error"});return}x(n,o)}),a.off("click",".btn-toggle-token").on("click",".btn-toggle-token",async function(){const e=$(this).data("id"),t=!!$(this).data("enabled");try{await m.apiTokens.update(e,{enabled:!t}),E.toast(`Token ${t?"disabled":"enabled"}.`,{type:"success"}),o()}catch(s){E.toast(`Update failed: ${s.message||s}`,{type:"error"})}}),a.off("click",".btn-revoke-token").on("click",".btn-revoke-token",async function(){const e=$(this).data("id"),t=$(this).data("name");if(await E.confirm(`Revoke token "${t}"? External callers using it will immediately lose access. This cannot be undone.`))try{await m.apiTokens.revoke(e),E.toast("Token revoked.",{type:"success"}),o()}catch(s){E.toast(`Revoke failed: ${s.message||s}`,{type:"error"})}})}};
8
+ `}]}),Domma.icons.scan(a.get(0)),g(a.get(0)),document.querySelectorAll("#api-tokens-table [data-tooltip]").forEach(e=>{E.tooltip(e,{content:e.getAttribute("data-tooltip"),position:"top"})});const o=()=>location.reload();a.off("click","#btn-new-token").on("click","#btn-new-token",()=>{if(!n.length){E.toast("No projects available \u2014 create a project first.",{type:"error"});return}x(n,o)}),a.off("click",".btn-toggle-token").on("click",".btn-toggle-token",async function(){const e=$(this).data("id"),t=!!$(this).data("enabled");try{await m.apiTokens.update(e,{enabled:!t}),E.toast(`Token ${t?"disabled":"enabled"}.`,{type:"success"}),o()}catch(s){E.toast(`Update failed: ${s.message||s}`,{type:"error"})}}),a.off("click",".btn-revoke-token").on("click",".btn-revoke-token",async function(){const e=$(this).data("id"),t=$(this).data("name");if(await E.confirm(`Revoke token "${t}"? External callers using it will immediately lose access. This cannot be undone.`))try{await m.apiTokens.revoke(e),E.toast("Token revoked.",{type:"success"}),o()}catch(s){E.toast(`Revoke failed: ${s.message||s}`,{type:"error"})}})}};
@@ -1 +1 @@
1
- import{api as h}from"../api.js";import{keepTableIconsAlive as M}from"../lib/table-icons.js";function L(t){return String(t).replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")}let u=null,b=null,d=[],N=1,w=[],x={};const O=50;export const collectionEntriesView={templateUrl:"/admin/js/templates/collection-entries.html",async onMount(t){d=[],N=1,b=null,w=[],x={};const i=window.location.hash.match(/\/collections\/([^/?#]+)\/entries/);if(u=i?i[1]:null,!u){E.toast("No collection selected.",{type:"error"});return}const s=E.loader(t.get(0),{type:"dots"});try{if(b=await h.collections.get(u),!b){s.destroy(),E.toast("Collection not found.",{type:"error"}),R.navigate("/collections");return}const o=t.find("#entries-title").get(0);o&&(o.textContent=b.title+" \u2014 Entries");const e=t.find("#edit-schema-btn").get(0);e&&e.setAttribute("href",`#/collections/edit/${u}`);const l=t.find("#form-btn").get(0);l&&(new Set(["roles","user-profiles"]).has(u)?l.style.display="none":l.setAttribute("href",`#/forms/edit/${u}`))}catch{s.destroy(),E.toast("Failed to load collection schema.",{type:"error"});return}try{w=await h.actions.forCollection(u)}catch{w=[]}try{await v(t)}finally{s.destroy()}await k(t);const r=t.find("#entry-search").get(0);r&&r.addEventListener("input",()=>{const o=r.value.toLowerCase().trim(),e=o?d.filter(l=>Object.values(l.data||{}).some(a=>String(a).toLowerCase().includes(o))):d;C(e,t)}),t.find("#add-entry-btn").off("click").on("click",()=>{A(null,t)}),t.find("#export-btn").off("click").on("click",()=>{const o=E.slideover({title:"Export Entries",size:"sm",position:"right"}),e=document.createElement("div");e.style.cssText="padding:1.25rem;display:flex;flex-direction:column;gap:1rem;",["json","csv"].forEach(l=>{const a=document.createElement("button");a.className="btn btn-ghost",a.style.cssText="justify-content:flex-start;gap:.5rem;",a.textContent=l==="json"?"Export as JSON":"Export as CSV",a.addEventListener("click",()=>{fetch(`/api/collections/${u}/export?format=${l}`,{headers:{Authorization:`Bearer ${S.get("auth_token")||""}`}}).then(c=>c.blob()).then(c=>{const n=document.createElement("a");n.href=URL.createObjectURL(c),n.download=`${u}-entries.${l}`,document.body.appendChild(n),n.click(),document.body.removeChild(n),URL.revokeObjectURL(n.href),o.close()}).catch(()=>E.toast("Export failed.",{type:"error"}))}),e.appendChild(a)}),o.setContent(e),o.open()}),t.find("#import-btn").off("click").on("click",()=>{I(t)}),t.find("#clear-all-btn").off("click").on("click",async()=>{if(await E.confirm("Delete ALL entries? This cannot be undone."))try{await h.collections.clearEntries(u),d=[],E.toast("All entries cleared.",{type:"success"}),C([],t)}catch{E.toast("Failed to clear entries.",{type:"error"})}}),t.off("click",".js-run-action").on("click",".js-run-action",async function(o){o.stopPropagation();const e=this,{actionSlug:l,entryId:a,confirmMessage:c}=e.dataset;if(!(c&&!await E.confirm(c))){e.disabled=!0;try{const n=await h.actions.execute(l,a);if(n.success)E.toast(`Action completed (${n.stepsCompleted} step${n.stepsCompleted!==1?"s":""}).`,{type:"success"}),await v(t);else{const p=n.results?.find(f=>!f.success);E.toast(p?.error||"Action failed.",{type:"error"})}}catch(n){E.toast(n.message||"Action failed.",{type:"error"})}finally{e.disabled=!1}}}),t.off("click",".js-edit-entry").on("click",".js-edit-entry",function(o){o.stopPropagation();const e=d.find(l=>l.id===this.dataset.id);e&&A(e,t)}),t.off("click",".js-delete-entry").on("click",".js-delete-entry",async function(o){o.stopPropagation();const e=this.dataset.id;if(await E.confirm("Delete this entry?"))try{await h.collections.deleteEntry(u,e),d=d.filter(a=>a.id!==e),E.toast("Entry deleted.",{type:"success"}),C(d,t)}catch{E.toast("Failed to delete entry.",{type:"error"})}}),t.off("click","#entries-table tbody tr").on("click","#entries-table tbody tr",function(o){if(o.target.closest(".js-run-action, .js-edit-entry, .js-delete-entry"))return;const e=this.querySelector(".js-edit-entry")?.dataset.id,l=d.find(a=>a.id===e);l&&B(l)}),Domma.icons.scan(),M(t.get(0))}};async function k(t){const m=w.filter(s=>s.access?.rowLevel);if(m.length===0)return;const i=d.map(s=>s.id);i.length!==0&&(await Promise.all(m.map(async s=>{try{const{allowed:r}=await h.actions.checkAccess(s.slug,i);x[s.slug]=new Set(r)}catch{x[s.slug]=null}})),C(d,t))}async function v(t){try{const m=await h.collections.listEntries(u,{limit:500});d=m.entries||m||[]}catch{d=[],E.toast("Could not load entries.",{type:"error"})}C(d,t),await k(t)}function C(t,m){const i=m.find("#entry-count").get(0);i&&(i.textContent=t.length===d.length?`${d.length} entr${d.length!==1?"ies":"y"}`:`Showing ${t.length} of ${d.length}`);const r=[...(b?.fields||[]).slice(0,5).map(e=>({key:`data.${e.name}`,title:e.label||e.name,render:(l,a)=>{const c=a.data?.[e.name]??"",n=String(c),p=document.createElement("span");return p.title=n,p.textContent=n.length>60?n.slice(0,60)+"\u2026":n,p.outerHTML}})),{key:"meta",title:"Created",render:e=>D(e?.createdAt).format("DD MMM YYYY HH:mm")},{key:"id",title:"",render:e=>{const l=document.createElement("div");l.style.cssText="display:flex;gap:.3rem;justify-content:flex-end;flex-wrap:wrap;",w.forEach(n=>{if(x[n.slug]!==void 0&&x[n.slug]!==null&&!x[n.slug].has(e))return;const p=document.createElement("button");p.className="btn btn-sm btn-ghost js-run-action",p.dataset.actionSlug=n.slug,p.dataset.entryId=e,p.dataset.confirmMessage=n.trigger?.confirmMessage||"",p.title=n.title||n.slug;const f=document.createElement("span");f.setAttribute("data-icon",L(n.trigger?.icon||"zap")),f.setAttribute("data-icon-size","14"),p.appendChild(f),p.appendChild(document.createTextNode(" "+(n.trigger?.label||n.title))),l.appendChild(p)});const a=document.createElement("button");a.className="btn btn-sm btn-ghost js-edit-entry",a.dataset.id=e,a.setAttribute("data-tooltip","Edit"),a.innerHTML='<span data-icon="edit" data-icon-size="14"></span>';const c=document.createElement("button");return c.className="btn btn-sm btn-ghost js-delete-entry",c.dataset.id=e,c.setAttribute("data-tooltip","Delete"),c.innerHTML='<span data-icon="trash" data-icon-size="14"></span>',l.appendChild(a),l.appendChild(c),l.outerHTML}}];T.create("#entries-table",{data:t,columns:r,emptyMessage:'No entries yet. Click "Add Entry" to get started.'});const o=document.querySelector("#entries-table");o&&(o.querySelectorAll("tbody tr").forEach(e=>{e.style.cursor="pointer"}),Domma.icons.scan(o),o.querySelectorAll("[data-tooltip]").forEach(e=>{E.tooltip(e,{content:e.getAttribute("data-tooltip"),position:"top"})}))}function z(t,m){const i={};return t.forEach(s=>{const r={};s.fullWidth?r.span=m:s.span>1&&(r.span=s.span),s.placeholder&&(r.placeholder=s.placeholder),s.helper&&(r.hint=s.helper);const o=s.type==="checkbox"?"boolean":s.type==="date"?"string":s.type;i[s.name]={type:o,label:s.label,required:s.required,options:s.options,...Object.keys(r).length?{formConfig:r}:{}}}),i}function A(t,m){const i=!!t,s=E.modal({title:i?"Edit Entry":"Add Entry",size:"md"}),r=document.createElement("div");r.style.cssText="padding:.25rem 0 .5rem;";const o=b?.fields||[],e=b?.columns||2,l=b?.layout||"stacked",a=document.createElement("div");F.create(z(o,e),t?.data||{},{layout:l,columns:e,showSubmitButton:!1}).renderTo(a),(o||[]).forEach(f=>{if(f.type!=="date"||!f.name)return;const y=a.querySelector(`[name="${f.name}"]`);y&&y.type!=="date"&&(y.type="date")}),r.appendChild(a);const c=document.createElement("div");c.style.cssText="display:flex;justify-content:flex-end;gap:.5rem;margin-top:.5rem;";const n=document.createElement("button");n.className="btn btn-ghost",n.textContent="Cancel";const p=document.createElement("button");p.className="btn btn-primary",p.textContent=i?"Save Changes":"Add Entry",c.appendChild(n),c.appendChild(p),r.appendChild(c),s.element.appendChild(r),s.open(),n.addEventListener("click",()=>s.close()),p.addEventListener("click",async()=>{const f={};o.forEach(y=>{const g=a.querySelector(`[name="${y.name}"]`);g&&(f[y.name]=y.type==="checkbox"?g.checked:g.value)}),p.disabled=!0;try{if(i){const y=await h.collections.updateEntry(u,t.id,f),g=d.findIndex(j=>j.id===t.id);g!==-1&&(d[g]=y),E.toast("Entry updated.",{type:"success"})}else{const y=await h.collections.createEntry(u,f);d.unshift(y),E.toast("Entry added.",{type:"success"})}s.close(),C(d,m||{find:()=>({get:()=>null})})}catch(y){E.toast(y.message||"Failed to save entry.",{type:"error"}),p.disabled=!1}})}function B(t){const m=document.createElement("div"),i=b?.fields||[],s=document.createElement("div");s.style.cssText="display:flex;flex-direction:column;gap:.75rem;margin-bottom:1.25rem;",i.forEach(e=>{const l=t.data?.[e.name];if(l==null||l==="")return;const a=document.createElement("div");a.style.cssText="border-bottom:1px solid var(--dm-border,#333);padding-bottom:.6rem;";const c=document.createElement("strong");c.textContent=e.label||e.name,c.style.cssText="display:block;font-size:.8rem;color:var(--dm-text-muted,#888);margin-bottom:.2rem;";const n=document.createElement("p");n.textContent=String(l),n.style.cssText="margin:0;word-break:break-word;",a.appendChild(c),a.appendChild(n),s.appendChild(a)});const r=document.createElement("div");if(r.style.cssText="font-size:.8rem;color:var(--dm-text-muted,#888);display:flex;flex-direction:column;gap:.3rem;border-top:1px solid var(--dm-border,#333);padding-top:.75rem;",t.meta?.createdAt){const e=document.createElement("span");e.textContent=`Created: ${D(t.meta.createdAt).format("DD MMM YYYY HH:mm")}`,r.appendChild(e)}if(t.meta?.source){const e=document.createElement("span");e.textContent=`Source: ${t.meta.source}`,r.appendChild(e)}m.appendChild(s),m.appendChild(r);const o=E.modal({title:"Entry Details",size:"md"});o.element.appendChild(m),o.open()}function I(t){const m=E.modal({title:"Import Entries",size:"md"}),i=document.createElement("div");i.style.cssText="padding:.25rem 0 .5rem;display:flex;flex-direction:column;gap:.75rem;";const s=document.createElement("p");s.textContent='Paste a JSON array of entries. Each item should have a "data" object with field values.',s.style.cssText="font-size:.875rem;color:var(--dm-text-muted,#888);margin:0;",i.appendChild(s);const r=document.createElement("textarea");r.className="form-input",r.rows=10,r.placeholder='[{"data": {"name": "Example"}}, ...]',i.appendChild(r);const o=document.createElement("p");o.style.cssText="font-size:.875rem;margin:0;display:none;",i.appendChild(o);const e=document.createElement("div");e.style.cssText="display:flex;justify-content:flex-end;gap:.5rem;";const l=document.createElement("button");l.className="btn btn-ghost",l.textContent="Cancel";const a=document.createElement("button");a.className="btn btn-primary",a.textContent="Import",e.appendChild(l),e.appendChild(a),i.appendChild(e),m.element.appendChild(i),m.open(),l.addEventListener("click",()=>m.close()),a.addEventListener("click",async()=>{let c;try{if(c=JSON.parse(r.value),!Array.isArray(c))throw new Error("Must be a JSON array")}catch(n){o.style.display="",o.style.color="var(--dm-danger,#f87171)",o.textContent=`Invalid JSON: ${n.message}`;return}a.disabled=!0;try{const n=await h.collections.import(u,c);o.style.display="",o.style.color="var(--dm-success,#4ade80)",o.textContent=`Imported ${n.imported} entries. Skipped: ${n.skipped}.`,await v(t||{find:()=>({get:()=>null,off:()=>({on:()=>{}})})}),setTimeout(()=>m.close(),1500)}catch(n){o.style.display="",o.style.color="var(--dm-danger,#f87171)",o.textContent=n.message||"Import failed."}finally{a.disabled=!1}})}
1
+ import{api as h}from"../api.js";import{keepTableIconsAlive as M}from"../lib/table-icons.js";function L(t){return String(t).replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")}let u=null,g=null,d=[],N=1,w=[],x={};const O=50;export const collectionEntriesView={templateUrl:"/admin/js/templates/collection-entries.html",async onMount(t){d=[],N=1,g=null,w=[],x={};const i=window.location.hash.match(/\/collections\/([^/?#]+)\/entries/);if(u=i?i[1]:null,!u){E.toast("No collection selected.",{type:"error"});return}const s=E.loader(t.get(0),{type:"dots"});try{if(g=await h.collections.get(u),!g){s.destroy(),E.toast("Collection not found.",{type:"error"}),R.navigate("/collections");return}const o=t.find("#entries-title").get(0);o&&(o.textContent=g.title+" \u2014 Entries");const e=t.find("#edit-schema-btn").get(0);e&&e.setAttribute("href",`#/collections/edit/${u}`);const r=t.find("#form-btn").get(0);r&&(new Set(["roles","user-profiles"]).has(u)?r.style.display="none":r.setAttribute("href",`#/forms/edit/${u}`))}catch{s.destroy(),E.toast("Failed to load collection schema.",{type:"error"});return}try{w=await h.actions.forCollection(u)}catch{w=[]}try{await v(t)}finally{s.destroy()}await k(t);const l=t.find("#entry-search").get(0);l&&l.addEventListener("input",()=>{const o=l.value.toLowerCase().trim(),e=o?d.filter(r=>Object.values(r.data||{}).some(a=>String(a).toLowerCase().includes(o))):d;C(e,t)}),t.find("#add-entry-btn").off("click").on("click",()=>{A(null,t)}),t.find("#export-btn").off("click").on("click",()=>{const o=E.slideover({title:"Export Entries",size:"sm",position:"right"}),e=document.createElement("div");e.style.cssText="padding:1.25rem;display:flex;flex-direction:column;gap:1rem;",["json","csv"].forEach(r=>{const a=document.createElement("button");a.className="btn btn-ghost",a.style.cssText="justify-content:flex-start;gap:.5rem;",a.textContent=r==="json"?"Export as JSON":"Export as CSV",a.addEventListener("click",()=>{fetch(`/api/collections/${u}/export?format=${r}`,{headers:{Authorization:`Bearer ${S.get("auth_token")||""}`}}).then(c=>c.blob()).then(c=>{const n=document.createElement("a");n.href=URL.createObjectURL(c),n.download=`${u}-entries.${r}`,document.body.appendChild(n),n.click(),document.body.removeChild(n),URL.revokeObjectURL(n.href),o.close()}).catch(()=>E.toast("Export failed.",{type:"error"}))}),e.appendChild(a)}),o.setContent(e),o.open()}),t.find("#import-btn").off("click").on("click",()=>{I(t)}),t.find("#clear-all-btn").off("click").on("click",async()=>{if(await E.confirm("Delete ALL entries? This cannot be undone."))try{await h.collections.clearEntries(u),d=[],E.toast("All entries cleared.",{type:"success"}),C([],t)}catch{E.toast("Failed to clear entries.",{type:"error"})}}),t.off("click",".js-run-action").on("click",".js-run-action",async function(o){o.stopPropagation();const e=this,{actionSlug:r,entryId:a,confirmMessage:c}=e.dataset;if(!(c&&!await E.confirm(c))){e.disabled=!0;try{const n=await h.actions.execute(r,a);if(n.success)E.toast(`Action completed (${n.stepsCompleted} step${n.stepsCompleted!==1?"s":""}).`,{type:"success"}),await v(t);else{const p=n.results?.find(f=>!f.success);E.toast(p?.error||"Action failed.",{type:"error"})}}catch(n){E.toast(n.message||"Action failed.",{type:"error"})}finally{e.disabled=!1}}}),t.off("click",".js-edit-entry").on("click",".js-edit-entry",function(o){o.stopPropagation();const e=d.find(r=>r.id===this.dataset.id);e&&A(e,t)}),t.off("click",".js-delete-entry").on("click",".js-delete-entry",async function(o){o.stopPropagation();const e=this.dataset.id;if(await E.confirm("Delete this entry?"))try{await h.collections.deleteEntry(u,e),d=d.filter(a=>a.id!==e),E.toast("Entry deleted.",{type:"success"}),C(d,t)}catch{E.toast("Failed to delete entry.",{type:"error"})}}),t.off("click","#entries-table tbody tr").on("click","#entries-table tbody tr",function(o){if(o.target.closest(".js-run-action, .js-edit-entry, .js-delete-entry"))return;const e=this.querySelector(".js-edit-entry")?.dataset.id,r=d.find(a=>a.id===e);r&&B(r)}),Domma.icons.scan(),M(t.get(0))}};async function k(t){const m=w.filter(s=>s.access?.rowLevel);if(m.length===0)return;const i=d.map(s=>s.id);i.length!==0&&(await Promise.all(m.map(async s=>{try{const{allowed:l}=await h.actions.checkAccess(s.slug,i);x[s.slug]=new Set(l)}catch{x[s.slug]=null}})),C(d,t))}async function v(t){try{const m=await h.collections.listEntries(u,{limit:500});d=m.entries||m||[]}catch{d=[],E.toast("Could not load entries.",{type:"error"})}C(d,t),await k(t)}function C(t,m){const i=m.find("#entry-count").get(0);i&&(i.textContent=t.length===d.length?`${d.length} entr${d.length!==1?"ies":"y"}`:`Showing ${t.length} of ${d.length}`);const l=[...(g?.fields||[]).slice(0,5).map(e=>({key:`data.${e.name}`,title:e.label||e.name,render:(r,a)=>{const c=a.data?.[e.name]??"",n=String(c),p=document.createElement("span");return p.title=n,p.textContent=n.length>60?n.slice(0,60)+"\u2026":n,p.outerHTML}})),{key:"meta",title:"Created",render:e=>D(e?.createdAt).format("DD MMM YYYY HH:mm")},{key:"id",title:"",render:e=>{const r=document.createElement("div");r.style.cssText="display:flex;gap:.3rem;justify-content:flex-end;flex-wrap:wrap;",w.forEach(n=>{if(x[n.slug]!==void 0&&x[n.slug]!==null&&!x[n.slug].has(e))return;const p=document.createElement("button");p.className="btn btn-sm btn-ghost js-run-action",p.dataset.actionSlug=n.slug,p.dataset.entryId=e,p.dataset.confirmMessage=n.trigger?.confirmMessage||"",p.title=n.title||n.slug;const f=document.createElement("span");f.setAttribute("data-icon",L(n.trigger?.icon||"zap")),f.setAttribute("data-icon-size","14"),p.appendChild(f),p.appendChild(document.createTextNode(" "+(n.trigger?.label||n.title))),r.appendChild(p)});const a=document.createElement("button");a.className="btn btn-sm btn-ghost js-edit-entry",a.dataset.id=e,a.setAttribute("data-tooltip","Edit"),a.innerHTML='<span data-icon="edit" data-icon-size="14"></span>';const c=document.createElement("button");return c.className="btn btn-sm btn-ghost js-delete-entry",c.dataset.id=e,c.setAttribute("data-tooltip","Delete"),c.innerHTML='<span data-icon="trash" data-icon-size="14"></span>',r.appendChild(a),r.appendChild(c),r.outerHTML}}];T.create("#entries-table",{data:t,columns:l,emptyMessage:'No entries yet. Click "Add Entry" to get started.'});const o=document.querySelector("#entries-table");o&&(o.querySelectorAll("tbody tr").forEach(e=>{e.style.cursor="pointer"}),Domma.icons.scan(o),o.querySelectorAll("[data-tooltip]").forEach(e=>{E.tooltip(e,{content:e.getAttribute("data-tooltip"),position:"top"})}))}function z(t,m){const i={};return t.forEach(s=>{const l={};s.fullWidth?l.span=m:s.span>1&&(l.span=s.span),s.placeholder&&(l.placeholder=s.placeholder),s.helper&&(l.hint=s.helper);const o=s.type==="checkbox"?"boolean":s.type==="date"?"string":s.type;i[s.name]={type:o,label:s.label,required:s.required,options:s.options,...Object.keys(l).length?{formConfig:l}:{}}}),i}function A(t,m){const i=!!t,s=E.slideover({title:i?"Edit Entry":"Add Entry",size:"md",position:"right"}),l=document.createElement("div");l.style.cssText="padding:.25rem 0 .5rem;";const o=g?.fields||[],e=g?.columns||2,r=g?.layout||"stacked",a=document.createElement("div");F.create(z(o,e),t?.data||{},{layout:r,columns:e,showSubmitButton:!1}).renderTo(a),(o||[]).forEach(f=>{if(f.type!=="date"||!f.name)return;const y=a.querySelector(`[name="${f.name}"]`);y&&y.type!=="date"&&(y.type="date")}),l.appendChild(a);const c=document.createElement("div");c.style.cssText="display:flex;justify-content:flex-end;gap:.5rem;margin-top:.5rem;";const n=document.createElement("button");n.className="btn btn-ghost",n.textContent="Cancel";const p=document.createElement("button");p.className="btn btn-primary",p.textContent=i?"Save Changes":"Add Entry",c.appendChild(n),c.appendChild(p),l.appendChild(c),s.element.appendChild(l),s.open(),n.addEventListener("click",()=>s.close()),p.addEventListener("click",async()=>{const f={};o.forEach(y=>{const b=a.querySelector(`[name="${y.name}"]`);b&&(f[y.name]=y.type==="checkbox"?b.checked:b.value)}),p.disabled=!0;try{if(i){const y=await h.collections.updateEntry(u,t.id,f),b=d.findIndex(j=>j.id===t.id);b!==-1&&(d[b]=y),E.toast("Entry updated.",{type:"success"})}else{const y=await h.collections.createEntry(u,f);d.unshift(y),E.toast("Entry added.",{type:"success"})}s.close(),C(d,m||{find:()=>({get:()=>null})})}catch(y){E.toast(y.message||"Failed to save entry.",{type:"error"}),p.disabled=!1}})}function B(t){const m=document.createElement("div"),i=g?.fields||[],s=document.createElement("div");s.style.cssText="display:flex;flex-direction:column;gap:.75rem;margin-bottom:1.25rem;",i.forEach(e=>{const r=t.data?.[e.name];if(r==null||r==="")return;const a=document.createElement("div");a.style.cssText="border-bottom:1px solid var(--dm-border,#333);padding-bottom:.6rem;";const c=document.createElement("strong");c.textContent=e.label||e.name,c.style.cssText="display:block;font-size:.8rem;color:var(--dm-text-muted,#888);margin-bottom:.2rem;";const n=document.createElement("p");n.textContent=String(r),n.style.cssText="margin:0;word-break:break-word;",a.appendChild(c),a.appendChild(n),s.appendChild(a)});const l=document.createElement("div");if(l.style.cssText="font-size:.8rem;color:var(--dm-text-muted,#888);display:flex;flex-direction:column;gap:.3rem;border-top:1px solid var(--dm-border,#333);padding-top:.75rem;",t.meta?.createdAt){const e=document.createElement("span");e.textContent=`Created: ${D(t.meta.createdAt).format("DD MMM YYYY HH:mm")}`,l.appendChild(e)}if(t.meta?.source){const e=document.createElement("span");e.textContent=`Source: ${t.meta.source}`,l.appendChild(e)}m.appendChild(s),m.appendChild(l);const o=E.slideover({title:"Entry Details",size:"md",position:"right"});o.element.appendChild(m),o.open()}function I(t){const m=E.slideover({title:"Import Entries",size:"md",position:"right"}),i=document.createElement("div");i.style.cssText="padding:.25rem 0 .5rem;display:flex;flex-direction:column;gap:.75rem;";const s=document.createElement("p");s.textContent='Paste a JSON array of entries. Each item should have a "data" object with field values.',s.style.cssText="font-size:.875rem;color:var(--dm-text-muted,#888);margin:0;",i.appendChild(s);const l=document.createElement("textarea");l.className="form-input",l.rows=10,l.placeholder='[{"data": {"name": "Example"}}, ...]',i.appendChild(l);const o=document.createElement("p");o.style.cssText="font-size:.875rem;margin:0;display:none;",i.appendChild(o);const e=document.createElement("div");e.style.cssText="display:flex;justify-content:flex-end;gap:.5rem;";const r=document.createElement("button");r.className="btn btn-ghost",r.textContent="Cancel";const a=document.createElement("button");a.className="btn btn-primary",a.textContent="Import",e.appendChild(r),e.appendChild(a),i.appendChild(e),m.element.appendChild(i),m.open(),r.addEventListener("click",()=>m.close()),a.addEventListener("click",async()=>{let c;try{if(c=JSON.parse(l.value),!Array.isArray(c))throw new Error("Must be a JSON array")}catch(n){o.style.display="",o.style.color="var(--dm-danger,#f87171)",o.textContent=`Invalid JSON: ${n.message}`;return}a.disabled=!0;try{const n=await h.collections.import(u,c);o.style.display="",o.style.color="var(--dm-success,#4ade80)",o.textContent=`Imported ${n.imported} entries. Skipped: ${n.skipped}.`,await v(t||{find:()=>({get:()=>null,off:()=>({on:()=>{}})})}),setTimeout(()=>m.close(),1500)}catch(n){o.style.display="",o.style.color="var(--dm-danger,#f87171)",o.textContent=n.message||"Import failed."}finally{a.disabled=!1}})}
@@ -1 +1 @@
1
- import{api as y}from"../api.js";import{filterByProject as M,getProjectFromHash as B}from"../lib/project-context.js";import{keepTableIconsAlive as A}from"../lib/table-icons.js";function h(e){return String(e).replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")}let d={};export const collectionsView={templateUrl:"/admin/js/templates/collections.html",async onMount(e){const s=E.loader(e.get(0),{type:"dots"});E.tabs(e.find("#collections-tabs").get(0));const p=e.find("#collections-tabs").get(0)?.querySelector(".tab-list");if(p){const t=e.find("#collections-header-actions").get(0);Array.from(p.querySelectorAll(".tab-item")).forEach((n,c)=>{n.addEventListener("click",()=>{t&&(t.style.display=c===0?"":"none")})})}await Promise.all([j(e),q(e)]),s.destroy(),A(e.get(0)),e.off("click",".js-delete-collection").on("click",".js-delete-collection",async function(){const t=this.dataset.slug,n=this.dataset.plugin,c=n?`This collection is managed by the <strong>${n}</strong> plugin. Deleting it may cause the plugin to malfunction. Continue?`:`Delete collection "${t}" and all its data? This cannot be undone.`;if(await E.confirm(c))try{await y.collections.delete(t),E.toast("Collection deleted.",{type:"success"}),await j(e)}catch{E.toast("Failed to delete collection.",{type:"error"})}}),e.find("#create-collection-btn").off("click").on("click",()=>{const t=E.modal({title:"New Collection",size:"sm"}),n=document.createElement("div");n.style.cssText="padding:.25rem 0 .5rem;";const c=document.createElement("div");F.create({title:{type:"string",label:"Collection Title",placeholder:"e.g. Products, Blog Posts\u2026",required:!0}},{},{showSubmitButton:!1}).renderTo(c),n.appendChild(c);const a=document.createElement("div");a.style.cssText="display:flex;justify-content:flex-end;gap:.5rem;margin-top:.75rem;";const o=document.createElement("button");o.className="btn btn-ghost",o.textContent="Cancel";const r=document.createElement("button");r.className="btn btn-primary",r.textContent="Create",a.appendChild(o),a.appendChild(r),n.appendChild(a),t.element.appendChild(n),t.open();const l=c.querySelector('input[name="title"]');setTimeout(()=>l?.focus(),50);async function i(){const m=l?.value.trim();if(m)try{const u=await y.collections.create({title:m});t.close(),R.navigate(`/collections/edit/${u.slug}`)}catch(u){E.toast(u.message||"Failed to create collection.",{type:"error"})}}o.addEventListener("click",()=>t.close()),r.addEventListener("click",i),l?.addEventListener("keydown",m=>{m.key==="Enter"&&i()})}),e.find("#connections-raw-toggle").on("change",function(){if(this.checked)d=x(e),v(e),e.find("#connections-form-view").hide(),e.find("#connections-raw-view").show();else{const t=e.find("#connections-raw-json").get(0)?.value||"{}";try{d=JSON.parse(t),C(e),e.find("#connections-form-view").show(),e.find("#connections-raw-view").hide()}catch{E.toast("Invalid JSON \u2014 fix before switching to form view.",{type:"warning"}),this.checked=!0}}}),e.find("#add-connection-btn").on("click",()=>{d=x(e);let t=Object.keys(d).length+1,n=`connection${t}`;for(;d[n];)t++,n=`connection${t}`;d[n]={type:"mongodb",uri:"",database:"",options:{}},C(e),v(e)}),e.find("#save-connections-btn").on("click",async()=>{const t=e.find("#connections-raw-toggle").get(0)?.checked;let n;if(t)try{n=JSON.parse(e.find("#connections-raw-json").get(0)?.value||"{}")}catch{E.toast("Invalid JSON \u2014 fix before saving.",{type:"warning"});return}else n=x(e);try{await y.collections.saveConnections(n),d=n,E.toast("Connections saved.",{type:"success"})}catch(c){E.toast(c.message||"Failed to save connections.",{type:"error"})}}),Domma.icons.scan()}};async function j(e){let s=[];try{s=await y.collections.list()}catch{E.toast("Could not load collections.",{type:"error"})}const p=B();p&&(s=M(s,p)),T.create("#collections-table",{data:s,columns:[{key:"title",title:"Title",render:(t,n)=>{const c=document.createElement("span");c.style.cssText="display:flex;align-items:center;gap:.4rem;";const a=document.createElement("a");if(a.href=`#/collections/${h(n.slug)}/entries`,a.textContent=t,a.style.fontWeight="600",c.appendChild(a),n.storage?.adapter==="mongodb"){const o=document.createElement("span");o.className="badge badge-outline",o.textContent="MongoDB",o.title=`Connection: ${n.storage.connection||"default"}`,o.style.cssText="font-size:0.65rem;padding:1px 6px;color:var(--dm-success,#16a34a);border-color:var(--dm-success,#16a34a);flex-shrink:0;",c.appendChild(o)}if(n.bundled){const o=document.createElement("span");o.className="badge badge-outline",o.textContent="Bundled",o.title="Seeded on fresh installs",o.style.cssText="font-size:0.65rem;padding:1px 6px;color:var(--dm-info,#2563eb);border-color:var(--dm-info,#2563eb);flex-shrink:0;",c.appendChild(o)}if(n.plugin){const o=document.createElement("span");o.className="badge badge-outline",o.textContent=n.plugin,o.title=`Managed by the ${n.plugin} plugin`,o.style.cssText="font-size:0.65rem;padding:1px 6px;color:var(--dm-warning,#d97706);border-color:var(--dm-warning,#d97706);flex-shrink:0;",c.appendChild(o)}return c.outerHTML}},{key:"slug",title:"Slug",render:t=>{const n=document.createElement("code");return n.textContent=t,n.outerHTML}},{key:"fields",title:"Field Count",render:t=>String(t?.length??0)},{key:"entryCount",title:"Entry Count",render:t=>String(t??0)},{key:"slug",title:"Actions",render:(t,n)=>{const c=document.createElement("div");c.style.cssText="display:flex;gap:.4rem;justify-content:flex-end;flex-wrap:wrap;";const a=document.createElement("a");a.href=`#/collections/edit/${h(t)}`,a.className="btn btn-sm btn-ghost",a.setAttribute("data-tooltip","Edit schema"),a.innerHTML='<span data-icon="edit-3" data-icon-size="14"></span>';const o=document.createElement("a");if(o.href=`#/collections/${h(t)}/entries`,o.className="btn btn-sm btn-ghost",o.setAttribute("data-tooltip","Entries"),o.innerHTML='<span data-icon="database" data-icon-size="14"></span>',c.appendChild(a),c.appendChild(o),!new Set(["roles","user-profiles"]).has(t)){const i=document.createElement("a");i.href=`#/forms/edit/${h(t)}`,i.className="btn btn-sm btn-ghost",i.setAttribute("data-tooltip","Form"),i.innerHTML='<span data-icon="layout" data-icon-size="14"></span>',c.appendChild(i)}const l=document.createElement("button");return l.className="btn btn-sm btn-ghost js-delete-collection",l.dataset.slug=t,l.dataset.plugin=n.plugin||"",l.setAttribute("data-tooltip","Delete"),l.innerHTML='<span data-icon="trash" data-icon-size="14"></span>',c.appendChild(l),c.outerHTML}}],emptyMessage:'No collections yet. Click "New Collection" to get started.'}),Domma.icons.scan(),document.querySelectorAll("#collections-table [data-tooltip]").forEach(t=>{E.tooltip(t,{content:t.getAttribute("data-tooltip"),position:"top"})})}async function q(e){try{d=await y.collections.getConnections()}catch{d={}}C(e),v(e)}function C(e){const s=e.find("#connections-list").get(0),p=e.find("#connections-empty").get(0);if(!s)return;s.textContent="";const t=Object.keys(d);p&&(p.style.display=t.length?"none":""),t.forEach(n=>{const c=d[n],a=document.createElement("div");a.className="conn-card card mb-3",a.dataset.connKey=n;const o=document.createElement("div");o.className="card-header",o.style.cssText="display:flex;align-items:center;gap:.5rem;";const r=document.createElement("input");r.type="text",r.className="form-input conn-name",r.value=n,r.placeholder="Connection name",r.style.cssText="flex:1;";const l=document.createElement("button");l.type="button",l.className="btn btn-sm btn-ghost",l.title="Delete connection",l.innerHTML='<span data-icon="trash"></span>',l.addEventListener("click",()=>{d=x(e);const L=r.value.trim();L&&delete d[L],C(e),v(e)}),o.appendChild(r),o.appendChild(l);const i=document.createElement("div");i.className="card-body",i.style.cssText="display:grid;grid-template-columns:1fr 2fr 1fr;gap:.6rem;";const m=document.createElement("div"),u=document.createElement("label");u.className="form-label",u.textContent="Type";const f=document.createElement("input");f.type="text",f.className="form-input conn-type",f.value=c.type||"mongodb",f.disabled=!0,m.appendChild(u),m.appendChild(f);const w=document.createElement("div"),N=document.createElement("label");N.className="form-label",N.textContent="URI";const g=document.createElement("input");g.type="text",g.className="form-input conn-uri",g.value=c.uri||"",g.placeholder="mongodb://localhost:27017",w.appendChild(N),w.appendChild(g);const S=document.createElement("div"),k=document.createElement("label");k.className="form-label",k.textContent="Database";const b=document.createElement("input");b.type="text",b.className="form-input conn-database",b.value=c.database||"",b.placeholder="mydb",S.appendChild(k),S.appendChild(b),i.appendChild(m),i.appendChild(w),i.appendChild(S),a.appendChild(o),a.appendChild(i),s.appendChild(a)}),Domma.icons.scan(s)}function x(e){const s={},p=e.find("#connections-list").get(0)?.querySelectorAll(".conn-card")||[];for(const t of p){const n=t.querySelector(".conn-name")?.value.trim(),c=t.querySelector(".conn-uri")?.value.trim()||"",a=t.querySelector(".conn-database")?.value.trim()||"";n&&(s[n]={type:"mongodb",uri:c,database:a,options:{}})}return s}function v(e){const s=e.find("#connections-raw-json").get(0);s&&(s.value=JSON.stringify(d,null,2))}
1
+ import{api as y}from"../api.js";import{filterByProject as M,getProjectFromHash as B}from"../lib/project-context.js";import{keepTableIconsAlive as A}from"../lib/table-icons.js";function h(e){return String(e).replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")}let d={};export const collectionsView={templateUrl:"/admin/js/templates/collections.html",async onMount(e){const s=E.loader(e.get(0),{type:"dots"});E.tabs(e.find("#collections-tabs").get(0));const p=e.find("#collections-tabs").get(0)?.querySelector(".tab-list");if(p){const t=e.find("#collections-header-actions").get(0);Array.from(p.querySelectorAll(".tab-item")).forEach((n,c)=>{n.addEventListener("click",()=>{t&&(t.style.display=c===0?"":"none")})})}await Promise.all([j(e),q(e)]),s.destroy(),A(e.get(0)),e.off("click",".js-delete-collection").on("click",".js-delete-collection",async function(){const t=this.dataset.slug,n=this.dataset.plugin,c=n?`This collection is managed by the <strong>${n}</strong> plugin. Deleting it may cause the plugin to malfunction. Continue?`:`Delete collection "${t}" and all its data? This cannot be undone.`;if(await E.confirm(c))try{await y.collections.delete(t),E.toast("Collection deleted.",{type:"success"}),await j(e)}catch{E.toast("Failed to delete collection.",{type:"error"})}}),e.find("#create-collection-btn").off("click").on("click",()=>{const t=E.slideover({title:"New Collection",size:"sm",position:"right"}),n=document.createElement("div");n.style.cssText="padding:.25rem 0 .5rem;";const c=document.createElement("div");F.create({title:{type:"string",label:"Collection Title",placeholder:"e.g. Products, Blog Posts\u2026",required:!0}},{},{showSubmitButton:!1}).renderTo(c),n.appendChild(c);const a=document.createElement("div");a.style.cssText="display:flex;justify-content:flex-end;gap:.5rem;margin-top:.75rem;";const o=document.createElement("button");o.className="btn btn-ghost",o.textContent="Cancel";const r=document.createElement("button");r.className="btn btn-primary",r.textContent="Create",a.appendChild(o),a.appendChild(r),n.appendChild(a),t.element.appendChild(n),t.open();const l=c.querySelector('input[name="title"]');setTimeout(()=>l?.focus(),50);async function i(){const m=l?.value.trim();if(m)try{const u=await y.collections.create({title:m});t.close(),R.navigate(`/collections/edit/${u.slug}`)}catch(u){E.toast(u.message||"Failed to create collection.",{type:"error"})}}o.addEventListener("click",()=>t.close()),r.addEventListener("click",i),l?.addEventListener("keydown",m=>{m.key==="Enter"&&i()})}),e.find("#connections-raw-toggle").on("change",function(){if(this.checked)d=x(e),v(e),e.find("#connections-form-view").hide(),e.find("#connections-raw-view").show();else{const t=e.find("#connections-raw-json").get(0)?.value||"{}";try{d=JSON.parse(t),C(e),e.find("#connections-form-view").show(),e.find("#connections-raw-view").hide()}catch{E.toast("Invalid JSON \u2014 fix before switching to form view.",{type:"warning"}),this.checked=!0}}}),e.find("#add-connection-btn").on("click",()=>{d=x(e);let t=Object.keys(d).length+1,n=`connection${t}`;for(;d[n];)t++,n=`connection${t}`;d[n]={type:"mongodb",uri:"",database:"",options:{}},C(e),v(e)}),e.find("#save-connections-btn").on("click",async()=>{const t=e.find("#connections-raw-toggle").get(0)?.checked;let n;if(t)try{n=JSON.parse(e.find("#connections-raw-json").get(0)?.value||"{}")}catch{E.toast("Invalid JSON \u2014 fix before saving.",{type:"warning"});return}else n=x(e);try{await y.collections.saveConnections(n),d=n,E.toast("Connections saved.",{type:"success"})}catch(c){E.toast(c.message||"Failed to save connections.",{type:"error"})}}),Domma.icons.scan()}};async function j(e){let s=[];try{s=await y.collections.list()}catch{E.toast("Could not load collections.",{type:"error"})}const p=B();p&&(s=M(s,p)),T.create("#collections-table",{data:s,columns:[{key:"title",title:"Title",render:(t,n)=>{const c=document.createElement("span");c.style.cssText="display:flex;align-items:center;gap:.4rem;";const a=document.createElement("a");if(a.href=`#/collections/${h(n.slug)}/entries`,a.textContent=t,a.style.fontWeight="600",c.appendChild(a),n.storage?.adapter==="mongodb"){const o=document.createElement("span");o.className="badge badge-outline",o.textContent="MongoDB",o.title=`Connection: ${n.storage.connection||"default"}`,o.style.cssText="font-size:0.65rem;padding:1px 6px;color:var(--dm-success,#16a34a);border-color:var(--dm-success,#16a34a);flex-shrink:0;",c.appendChild(o)}if(n.bundled){const o=document.createElement("span");o.className="badge badge-outline",o.textContent="Bundled",o.title="Seeded on fresh installs",o.style.cssText="font-size:0.65rem;padding:1px 6px;color:var(--dm-info,#2563eb);border-color:var(--dm-info,#2563eb);flex-shrink:0;",c.appendChild(o)}if(n.plugin){const o=document.createElement("span");o.className="badge badge-outline",o.textContent=n.plugin,o.title=`Managed by the ${n.plugin} plugin`,o.style.cssText="font-size:0.65rem;padding:1px 6px;color:var(--dm-warning,#d97706);border-color:var(--dm-warning,#d97706);flex-shrink:0;",c.appendChild(o)}return c.outerHTML}},{key:"slug",title:"Slug",render:t=>{const n=document.createElement("code");return n.textContent=t,n.outerHTML}},{key:"fields",title:"Field Count",render:t=>String(t?.length??0)},{key:"entryCount",title:"Entry Count",render:t=>String(t??0)},{key:"slug",title:"Actions",render:(t,n)=>{const c=document.createElement("div");c.style.cssText="display:flex;gap:.4rem;justify-content:flex-end;flex-wrap:wrap;";const a=document.createElement("a");a.href=`#/collections/edit/${h(t)}`,a.className="btn btn-sm btn-ghost",a.setAttribute("data-tooltip","Edit schema"),a.innerHTML='<span data-icon="edit-3" data-icon-size="14"></span>';const o=document.createElement("a");if(o.href=`#/collections/${h(t)}/entries`,o.className="btn btn-sm btn-ghost",o.setAttribute("data-tooltip","Entries"),o.innerHTML='<span data-icon="database" data-icon-size="14"></span>',c.appendChild(a),c.appendChild(o),!new Set(["roles","user-profiles"]).has(t)){const i=document.createElement("a");i.href=`#/forms/edit/${h(t)}`,i.className="btn btn-sm btn-ghost",i.setAttribute("data-tooltip","Form"),i.innerHTML='<span data-icon="layout" data-icon-size="14"></span>',c.appendChild(i)}const l=document.createElement("button");return l.className="btn btn-sm btn-ghost js-delete-collection",l.dataset.slug=t,l.dataset.plugin=n.plugin||"",l.setAttribute("data-tooltip","Delete"),l.innerHTML='<span data-icon="trash" data-icon-size="14"></span>',c.appendChild(l),c.outerHTML}}],emptyMessage:'No collections yet. Click "New Collection" to get started.'}),Domma.icons.scan(),document.querySelectorAll("#collections-table [data-tooltip]").forEach(t=>{E.tooltip(t,{content:t.getAttribute("data-tooltip"),position:"top"})})}async function q(e){try{d=await y.collections.getConnections()}catch{d={}}C(e),v(e)}function C(e){const s=e.find("#connections-list").get(0),p=e.find("#connections-empty").get(0);if(!s)return;s.textContent="";const t=Object.keys(d);p&&(p.style.display=t.length?"none":""),t.forEach(n=>{const c=d[n],a=document.createElement("div");a.className="conn-card card mb-3",a.dataset.connKey=n;const o=document.createElement("div");o.className="card-header",o.style.cssText="display:flex;align-items:center;gap:.5rem;";const r=document.createElement("input");r.type="text",r.className="form-input conn-name",r.value=n,r.placeholder="Connection name",r.style.cssText="flex:1;";const l=document.createElement("button");l.type="button",l.className="btn btn-sm btn-ghost",l.title="Delete connection",l.innerHTML='<span data-icon="trash"></span>',l.addEventListener("click",()=>{d=x(e);const L=r.value.trim();L&&delete d[L],C(e),v(e)}),o.appendChild(r),o.appendChild(l);const i=document.createElement("div");i.className="card-body",i.style.cssText="display:grid;grid-template-columns:1fr 2fr 1fr;gap:.6rem;";const m=document.createElement("div"),u=document.createElement("label");u.className="form-label",u.textContent="Type";const f=document.createElement("input");f.type="text",f.className="form-input conn-type",f.value=c.type||"mongodb",f.disabled=!0,m.appendChild(u),m.appendChild(f);const w=document.createElement("div"),N=document.createElement("label");N.className="form-label",N.textContent="URI";const g=document.createElement("input");g.type="text",g.className="form-input conn-uri",g.value=c.uri||"",g.placeholder="mongodb://localhost:27017",w.appendChild(N),w.appendChild(g);const S=document.createElement("div"),k=document.createElement("label");k.className="form-label",k.textContent="Database";const b=document.createElement("input");b.type="text",b.className="form-input conn-database",b.value=c.database||"",b.placeholder="mydb",S.appendChild(k),S.appendChild(b),i.appendChild(m),i.appendChild(w),i.appendChild(S),a.appendChild(o),a.appendChild(i),s.appendChild(a)}),Domma.icons.scan(s)}function x(e){const s={},p=e.find("#connections-list").get(0)?.querySelectorAll(".conn-card")||[];for(const t of p){const n=t.querySelector(".conn-name")?.value.trim(),c=t.querySelector(".conn-uri")?.value.trim()||"",a=t.querySelector(".conn-database")?.value.trim()||"";n&&(s[n]={type:"mongodb",uri:c,database:a,options:{}})}return s}function v(e){const s=e.find("#connections-raw-json").get(0);s&&(s.value=JSON.stringify(d,null,2))}
@@ -1 +1 @@
1
- import{apiRequest as x}from"/admin/js/api.js";import{keepTableIconsAlive as A}from"/admin/js/lib/table-icons.js";let c=null,k=[],r=[];export const formSubmissionsView={templateUrl:"/admin/js/templates/form-submissions.html",async onMount(e){const l=window.location.hash.match(/\/forms\/([^/?#]+)\/submissions/);if(c=l?l[1]:null,!c){E.toast("No form selected.",{type:"error"});return}const u=E.loader(e.get(0),{type:"dots"});try{const o=await x(`/forms/${c}`);k=(o.fields||[]).filter(s=>s.type!=="page-break"),e.find("#submissions-title").get(0).textContent=`${o.title} \u2014 Submissions`}catch{E.toast("Could not load form definition.",{type:"error"})}try{await M(e)}finally{u.destroy()}A(e.get(0)),e.off("click",".js-delete-submission").on("click",".js-delete-submission",async function(o){o.stopPropagation();const s=this.dataset.id;if(await E.confirm("Delete this submission?"))try{await x(`/forms/${c}/submissions/${s}`,{method:"DELETE"}),r=r.filter(d=>d.id!==s),E.toast("Submission deleted.",{type:"success"}),v(r,e)}catch{E.toast("Failed to delete submission.",{type:"error"})}}),e.find("#export-btn").off("click").on("click",()=>{const o=E.slideover({title:"Export Submissions",size:"sm",position:"right"}),s=document.createElement("div");s.style.cssText="padding:1.25rem;display:flex;flex-direction:column;gap:1rem;";function i(d,y,f,g){const C=document.createElement("div");C.style.cssText="display:flex;flex-direction:column;gap:.4rem;";const b=document.createElement("button");b.className="btn btn-ghost",b.style.cssText="justify-content:flex-start;gap:.5rem;";const L=document.createElement("span");return L.setAttribute("data-icon",y),b.appendChild(L),b.appendChild(document.createTextNode(" "+d)),b.addEventListener("click",()=>{fetch(f,{headers:{Authorization:"Bearer "+(S.get("auth_token")||"")}}).then(w=>w.blob()).then(w=>{const h=document.createElement("a");h.href=URL.createObjectURL(w),h.download=g,document.body.appendChild(h),h.click(),document.body.removeChild(h),URL.revokeObjectURL(h.href),o.close()}).catch(()=>E.toast("Export failed.",{type:"error"}))}),C.appendChild(b),C}s.appendChild(i("Export as CSV","file-text",`/api/forms/${c}/submissions/export`,`${c}-submissions.csv`)),s.appendChild(i("Export as JSON","code",`/api/forms/${c}/submissions/export/json`,`${c}-submissions.json`)),o.setContent(s),Domma.icons.scan(s),o.open()}),e.find("#clear-all-btn").off("click").on("click",async()=>{if(await E.confirm("Delete all submissions? This cannot be undone."))try{await x(`/forms/${c}/submissions`,{method:"DELETE"}),r=[],E.toast("All submissions cleared.",{type:"success"}),v([],e)}catch{E.toast("Failed to clear submissions.",{type:"error"})}});const m=e.find("#sub-search").get(0),n=e.find("#sub-date-from").get(0),t=e.find("#sub-date-to").get(0);function a(){const o=m?.value.toLowerCase()||"",s=n?.value?new Date(n.value):null,i=t?.value?new Date(t.value+"T23:59:59"):null,d=r.filter(y=>{if(o&&!Object.values(y.data||{}).map(g=>String(g).toLowerCase()).some(g=>g.includes(o)))return!1;if(s||i){const f=y.meta?.createdAt?new Date(y.meta.createdAt):null;if(!f||s&&f<s||i&&f>i)return!1}return!0});v(d,e)}m&&m.addEventListener("input",a),n&&n.addEventListener("change",a),t&&t.addEventListener("change",a),Domma.icons.scan()}};async function M(e){try{r=await x(`/forms/${c}/submissions`)}catch{r=[],E.toast("Could not load submissions.",{type:"error"})}v(r,e)}function v(e,p){const l=p.find("#sub-count").get(0);l&&(e.length===r.length?l.textContent=`${r.length} submission${r.length!==1?"s":""}`:l.textContent=`Showing ${e.length} of ${r.length}`);const m=[...k.map(t=>({key:`data.${t.name}`,title:t.label||t.name,render:(a,o)=>{const s=o.data?.[t.name]??"",i=String(s),d=document.createElement("span");return d.title=i,d.textContent=i.length>80?i.slice(0,80)+"\u2026":i,d.outerHTML}})),{key:"meta",title:"Date",render:t=>D(t?.createdAt).format("DD MMM YYYY HH:mm")},{key:"id",title:"",render:t=>{const a=document.createElement("button");return a.className="btn btn-sm btn-ghost js-delete-submission",a.dataset.id=t,a.setAttribute("data-tooltip","Delete"),a.innerHTML='<span data-icon="trash" data-icon-size="14"></span>',a.outerHTML}}];T.create("#submissions-table",{data:e,columns:m,emptyMessage:"No submissions yet."});const n=document.querySelector("#submissions-table");n&&(n.querySelectorAll("tbody tr").forEach((t,a)=>{const o=e[a];o&&(t.style.cursor="pointer",t.addEventListener("click",s=>{s.target.closest(".js-delete-submission")||j(o)}))}),Domma.icons.scan(n),n.querySelectorAll("[data-tooltip]").forEach(t=>{E.tooltip(t,{content:t.getAttribute("data-tooltip"),position:"top"})}))}function j(e){const p=document.createElement("div"),l=document.createElement("div");l.style.cssText="display:flex;flex-direction:column;gap:.75rem;margin-bottom:1.25rem;",k.forEach(n=>{const t=e.data?.[n.name];if(t==null||t==="")return;const a=document.createElement("div");a.style.cssText="border-bottom:1px solid var(--dm-border,#333);padding-bottom:.6rem;";const o=document.createElement("strong");o.textContent=n.label||n.name,o.style.cssText="display:block;font-size:.8rem;color:var(--dm-text-muted,#888);margin-bottom:.2rem;";const s=document.createElement("p");s.textContent=String(t),s.style.cssText="margin:0;word-break:break-word;",a.appendChild(o),a.appendChild(s),l.appendChild(a)});const u=document.createElement("div");if(u.style.cssText="font-size:.8rem;color:var(--dm-text-muted,#888);display:flex;flex-direction:column;gap:.3rem;border-top:1px solid var(--dm-border,#333);padding-top:.75rem;",e.meta?.createdAt){const n=document.createElement("span");n.textContent=`Submitted: ${D(e.meta.createdAt).format("DD MMM YYYY HH:mm")}`,u.appendChild(n)}if(e.meta?.ip){const n=document.createElement("span");n.textContent=`IP: ${e.meta.ip}`,u.appendChild(n)}p.appendChild(l),p.appendChild(u);const m=E.modal({title:"Submission Details",size:"md"});m.element.appendChild(p),m.open()}
1
+ import{apiRequest as x}from"/admin/js/api.js";import{keepTableIconsAlive as A}from"/admin/js/lib/table-icons.js";let c=null,k=[],r=[];export const formSubmissionsView={templateUrl:"/admin/js/templates/form-submissions.html",async onMount(e){const l=window.location.hash.match(/\/forms\/([^/?#]+)\/submissions/);if(c=l?l[1]:null,!c){E.toast("No form selected.",{type:"error"});return}const u=E.loader(e.get(0),{type:"dots"});try{const o=await x(`/forms/${c}`);k=(o.fields||[]).filter(s=>s.type!=="page-break"),e.find("#submissions-title").get(0).textContent=`${o.title} \u2014 Submissions`}catch{E.toast("Could not load form definition.",{type:"error"})}try{await M(e)}finally{u.destroy()}A(e.get(0)),e.off("click",".js-delete-submission").on("click",".js-delete-submission",async function(o){o.stopPropagation();const s=this.dataset.id;if(await E.confirm("Delete this submission?"))try{await x(`/forms/${c}/submissions/${s}`,{method:"DELETE"}),r=r.filter(d=>d.id!==s),E.toast("Submission deleted.",{type:"success"}),v(r,e)}catch{E.toast("Failed to delete submission.",{type:"error"})}}),e.find("#export-btn").off("click").on("click",()=>{const o=E.slideover({title:"Export Submissions",size:"sm",position:"right"}),s=document.createElement("div");s.style.cssText="padding:1.25rem;display:flex;flex-direction:column;gap:1rem;";function i(d,y,f,g){const C=document.createElement("div");C.style.cssText="display:flex;flex-direction:column;gap:.4rem;";const b=document.createElement("button");b.className="btn btn-ghost",b.style.cssText="justify-content:flex-start;gap:.5rem;";const L=document.createElement("span");return L.setAttribute("data-icon",y),b.appendChild(L),b.appendChild(document.createTextNode(" "+d)),b.addEventListener("click",()=>{fetch(f,{headers:{Authorization:"Bearer "+(S.get("auth_token")||"")}}).then(w=>w.blob()).then(w=>{const h=document.createElement("a");h.href=URL.createObjectURL(w),h.download=g,document.body.appendChild(h),h.click(),document.body.removeChild(h),URL.revokeObjectURL(h.href),o.close()}).catch(()=>E.toast("Export failed.",{type:"error"}))}),C.appendChild(b),C}s.appendChild(i("Export as CSV","file-text",`/api/forms/${c}/submissions/export`,`${c}-submissions.csv`)),s.appendChild(i("Export as JSON","code",`/api/forms/${c}/submissions/export/json`,`${c}-submissions.json`)),o.setContent(s),Domma.icons.scan(s),o.open()}),e.find("#clear-all-btn").off("click").on("click",async()=>{if(await E.confirm("Delete all submissions? This cannot be undone."))try{await x(`/forms/${c}/submissions`,{method:"DELETE"}),r=[],E.toast("All submissions cleared.",{type:"success"}),v([],e)}catch{E.toast("Failed to clear submissions.",{type:"error"})}});const m=e.find("#sub-search").get(0),n=e.find("#sub-date-from").get(0),t=e.find("#sub-date-to").get(0);function a(){const o=m?.value.toLowerCase()||"",s=n?.value?new Date(n.value):null,i=t?.value?new Date(t.value+"T23:59:59"):null,d=r.filter(y=>{if(o&&!Object.values(y.data||{}).map(g=>String(g).toLowerCase()).some(g=>g.includes(o)))return!1;if(s||i){const f=y.meta?.createdAt?new Date(y.meta.createdAt):null;if(!f||s&&f<s||i&&f>i)return!1}return!0});v(d,e)}m&&m.addEventListener("input",a),n&&n.addEventListener("change",a),t&&t.addEventListener("change",a),Domma.icons.scan()}};async function M(e){try{r=await x(`/forms/${c}/submissions`)}catch{r=[],E.toast("Could not load submissions.",{type:"error"})}v(r,e)}function v(e,p){const l=p.find("#sub-count").get(0);l&&(e.length===r.length?l.textContent=`${r.length} submission${r.length!==1?"s":""}`:l.textContent=`Showing ${e.length} of ${r.length}`);const m=[...k.map(t=>({key:`data.${t.name}`,title:t.label||t.name,render:(a,o)=>{const s=o.data?.[t.name]??"",i=String(s),d=document.createElement("span");return d.title=i,d.textContent=i.length>80?i.slice(0,80)+"\u2026":i,d.outerHTML}})),{key:"meta",title:"Date",render:t=>D(t?.createdAt).format("DD MMM YYYY HH:mm")},{key:"id",title:"",render:t=>{const a=document.createElement("button");return a.className="btn btn-sm btn-ghost js-delete-submission",a.dataset.id=t,a.setAttribute("data-tooltip","Delete"),a.innerHTML='<span data-icon="trash" data-icon-size="14"></span>',a.outerHTML}}];T.create("#submissions-table",{data:e,columns:m,emptyMessage:"No submissions yet."});const n=document.querySelector("#submissions-table");n&&(n.querySelectorAll("tbody tr").forEach((t,a)=>{const o=e[a];o&&(t.style.cursor="pointer",t.addEventListener("click",s=>{s.target.closest(".js-delete-submission")||j(o)}))}),Domma.icons.scan(n),n.querySelectorAll("[data-tooltip]").forEach(t=>{E.tooltip(t,{content:t.getAttribute("data-tooltip"),position:"top"})}))}function j(e){const p=document.createElement("div"),l=document.createElement("div");l.style.cssText="display:flex;flex-direction:column;gap:.75rem;margin-bottom:1.25rem;",k.forEach(n=>{const t=e.data?.[n.name];if(t==null||t==="")return;const a=document.createElement("div");a.style.cssText="border-bottom:1px solid var(--dm-border,#333);padding-bottom:.6rem;";const o=document.createElement("strong");o.textContent=n.label||n.name,o.style.cssText="display:block;font-size:.8rem;color:var(--dm-text-muted,#888);margin-bottom:.2rem;";const s=document.createElement("p");s.textContent=String(t),s.style.cssText="margin:0;word-break:break-word;",a.appendChild(o),a.appendChild(s),l.appendChild(a)});const u=document.createElement("div");if(u.style.cssText="font-size:.8rem;color:var(--dm-text-muted,#888);display:flex;flex-direction:column;gap:.3rem;border-top:1px solid var(--dm-border,#333);padding-top:.75rem;",e.meta?.createdAt){const n=document.createElement("span");n.textContent=`Submitted: ${D(e.meta.createdAt).format("DD MMM YYYY HH:mm")}`,u.appendChild(n)}if(e.meta?.ip){const n=document.createElement("span");n.textContent=`IP: ${e.meta.ip}`,u.appendChild(n)}p.appendChild(l),p.appendChild(u);const m=E.slideover({title:"Submission Details",size:"md",position:"right"});m.element.appendChild(p),m.open()}
@@ -1 +1 @@
1
- import{apiRequest as p}from"/admin/js/api.js";import{filterByProject as b,getProjectFromHash as h}from"/admin/js/lib/project-context.js";import{keepTableIconsAlive as C}from"/admin/js/lib/table-icons.js";function u(d){return String(d).replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;").replace(/'/g,"&#39;")}export const formsView={templateUrl:"/admin/js/templates/forms.html",async onMount(d){await g(d),C(d.get(0)),d.off("click",".js-delete-form").on("click",".js-delete-form",async function(){const r=this.dataset.slug,e=this.dataset.plugin,c=e?`This form is managed by the <strong>${e}</strong> plugin. Deleting it may cause the plugin to malfunction. Continue?`:`Delete form "${r}" and all its submissions? This cannot be undone.`;if(await E.confirm(c))try{await p(`/forms/${r}`,{method:"DELETE"}),E.toast("Form deleted.",{type:"success"}),await g(d)}catch{E.toast("Failed to delete form.",{type:"error"})}}),d.off("click",".js-clone-form").on("click",".js-clone-form",async function(){const r=this.dataset.slug;try{const e=await p(`/forms/${r}`),{collection:c,...t}=e.actions||{},n=await p("/forms",{method:"POST",body:JSON.stringify({title:`${e.title} (Copy)`,description:e.description||"",fields:e.fields||[],settings:e.settings||{},actions:t})});E.toast(`Form duplicated as "${n.title}".`,{type:"success"}),R.navigate(`/forms/edit/${n.slug}`)}catch(e){E.toast(e.message||"Failed to duplicate form.",{type:"error"})}}),d.find("#create-form-btn").off("click").on("click",()=>{const r=E.modal({title:"Create Form",size:"sm"}),e=document.createElement("div");e.style.cssText="padding:.25rem 0 .5rem;";const c=document.createElement("div");F.create({title:{type:"string",label:"Form Title",placeholder:"e.g. Contact, Feedback\u2026",required:!0}},{},{showSubmitButton:!1}).renderTo(c),e.appendChild(c);const t=document.createElement("div");t.style.cssText="display:flex;justify-content:flex-end;gap:.5rem;margin-top:.75rem;";const n=document.createElement("button");n.className="btn btn-ghost",n.textContent="Cancel";const o=document.createElement("button");o.className="btn btn-primary",o.textContent="Create",t.appendChild(n),t.appendChild(o),e.appendChild(t),r.element.appendChild(e),r.open();const s=c.querySelector('input[name="title"]');setTimeout(()=>s?.focus(),50);async function a(){const i=s?.value.trim();if(i)try{const l=await p("/forms",{method:"POST",body:JSON.stringify({title:i})});r.close(),R.navigate(`/forms/edit/${l.slug}`)}catch(l){E.toast(l.message||"Failed to create form.",{type:"error"})}}n.addEventListener("click",()=>r.close()),o.addEventListener("click",a),s?.addEventListener("keydown",i=>{i.key==="Enter"&&a()})}),d.find("#test-email-btn").off("click").on("click",async()=>{const e=(window.__CMS_SITE__?.smtp||{}).fromAddress||"",c=E.modal({title:"Send Test Email",size:"sm"}),t=document.createElement("div");t.style.cssText="padding:.25rem 0 .5rem;";const n=document.createElement("div");n.className="mb-3";const o=document.createElement("label");o.className="form-label",o.textContent="Send to";const s=document.createElement("input");s.type="email",s.className="form-input",s.value=e,s.placeholder="test@example.com",n.appendChild(o),n.appendChild(s),t.appendChild(n);const a=document.createElement("p");a.className="text-muted",a.style.cssText="font-size:.8rem;margin-bottom:.75rem;",a.textContent="SMTP settings are configured in Site Settings.",t.appendChild(a);const i=document.createElement("div");i.style.cssText="display:flex;justify-content:flex-end;gap:.5rem;margin-top:.75rem;";const l=document.createElement("button");l.className="btn btn-ghost",l.textContent="Cancel";const m=document.createElement("button");m.className="btn btn-primary",m.textContent="Send",i.appendChild(l),i.appendChild(m),t.appendChild(i),c.element.appendChild(t),c.open(),setTimeout(()=>s?.focus(),50),l.addEventListener("click",()=>c.close()),m.addEventListener("click",async()=>{const f=s.value.trim();if(f){m.disabled=!0;try{await p("/forms/test-email",{method:"POST",body:JSON.stringify({to:f})}),c.close(),E.toast("Test email sent.",{type:"success"})}catch(y){E.toast(y.message||"Failed to send test email.",{type:"error"})}finally{m.disabled=!1}}})}),Domma.icons.scan()}};async function g(d){const r=E.loader(d.get(0),{type:"dots"});let e=[];try{e=await p("/forms")}catch{E.toast("Could not load forms.",{type:"error"})}finally{r.destroy()}const c=h();c&&(e=b(e,c)),T.create("#forms-table",{data:e,columns:[{key:"title",title:"Title",render:(t,n)=>{const o=document.createElement("span");o.style.cssText="display:inline-flex;align-items:center;gap:.4rem;flex-wrap:wrap;";const s=document.createElement("a");if(s.href=`#/forms/edit/${u(n.slug)}`,s.textContent=t,s.style.fontWeight="600",o.appendChild(s),n.plugin){const a=document.createElement("span");a.className="badge badge-outline",a.textContent=n.plugin,a.title=`Managed by the ${n.plugin} plugin`,a.style.cssText="font-size:0.65rem;padding:1px 6px;color:var(--dm-warning,#d97706);border-color:var(--dm-warning,#d97706);flex-shrink:0;",o.appendChild(a)}return o.outerHTML}},{key:"slug",title:"Slug",render:t=>{const n=document.createElement("code");return n.textContent=t,n.outerHTML}},{key:"fields",title:"Field Count",render:t=>String(t?.length??0)},{key:"submissionCount",title:"Submission Count",render:t=>String(t??0)},{key:"slug",title:"Actions",render:(t,n)=>{const o=document.createElement("div");o.style.cssText="display:flex;gap:.4rem;justify-content:flex-end;";const s=document.createElement("a");s.href=`#/forms/edit/${u(t)}`,s.className="btn btn-sm btn-ghost",s.setAttribute("data-tooltip","Edit"),s.innerHTML='<span data-icon="edit" data-icon-size="14"></span>';const a=document.createElement("a");a.href=`#/forms/${u(t)}/submissions`,a.className="btn btn-sm btn-ghost",a.setAttribute("data-tooltip","Submissions"),a.innerHTML='<span data-icon="inbox" data-icon-size="14"></span>';const i=document.createElement("button");i.className="btn btn-sm btn-ghost js-clone-form",i.dataset.slug=t,i.setAttribute("data-tooltip","Duplicate"),i.innerHTML='<span data-icon="copy" data-icon-size="14"></span>';const l=document.createElement("button");return l.className="btn btn-sm btn-ghost js-delete-form",l.dataset.slug=t,l.dataset.plugin=n.plugin||"",l.setAttribute("data-tooltip","Delete"),l.innerHTML='<span data-icon="trash" data-icon-size="14"></span>',o.appendChild(s),o.appendChild(a),o.appendChild(i),o.appendChild(l),o.outerHTML}}],emptyMessage:'No forms yet. Click "Create Form" to get started.'}),Domma.icons.scan(),document.querySelectorAll("#forms-table [data-tooltip]").forEach(t=>{E.tooltip(t,{content:t.getAttribute("data-tooltip"),position:"top"})})}
1
+ import{apiRequest as p}from"/admin/js/api.js";import{filterByProject as b,getProjectFromHash as h}from"/admin/js/lib/project-context.js";import{keepTableIconsAlive as C}from"/admin/js/lib/table-icons.js";function u(d){return String(d).replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;").replace(/'/g,"&#39;")}export const formsView={templateUrl:"/admin/js/templates/forms.html",async onMount(d){await g(d),C(d.get(0)),d.off("click",".js-delete-form").on("click",".js-delete-form",async function(){const c=this.dataset.slug,e=this.dataset.plugin,r=e?`This form is managed by the <strong>${e}</strong> plugin. Deleting it may cause the plugin to malfunction. Continue?`:`Delete form "${c}" and all its submissions? This cannot be undone.`;if(await E.confirm(r))try{await p(`/forms/${c}`,{method:"DELETE"}),E.toast("Form deleted.",{type:"success"}),await g(d)}catch{E.toast("Failed to delete form.",{type:"error"})}}),d.off("click",".js-clone-form").on("click",".js-clone-form",async function(){const c=this.dataset.slug;try{const e=await p(`/forms/${c}`),{collection:r,...t}=e.actions||{},n=await p("/forms",{method:"POST",body:JSON.stringify({title:`${e.title} (Copy)`,description:e.description||"",fields:e.fields||[],settings:e.settings||{},actions:t})});E.toast(`Form duplicated as "${n.title}".`,{type:"success"}),R.navigate(`/forms/edit/${n.slug}`)}catch(e){E.toast(e.message||"Failed to duplicate form.",{type:"error"})}}),d.find("#create-form-btn").off("click").on("click",()=>{const c=E.slideover({title:"Create Form",size:"sm",position:"right"}),e=document.createElement("div");e.style.cssText="padding:.25rem 0 .5rem;";const r=document.createElement("div");F.create({title:{type:"string",label:"Form Title",placeholder:"e.g. Contact, Feedback\u2026",required:!0}},{},{showSubmitButton:!1}).renderTo(r),e.appendChild(r);const t=document.createElement("div");t.style.cssText="display:flex;justify-content:flex-end;gap:.5rem;margin-top:.75rem;";const n=document.createElement("button");n.className="btn btn-ghost",n.textContent="Cancel";const o=document.createElement("button");o.className="btn btn-primary",o.textContent="Create",t.appendChild(n),t.appendChild(o),e.appendChild(t),c.element.appendChild(e),c.open();const s=r.querySelector('input[name="title"]');setTimeout(()=>s?.focus(),50);async function a(){const i=s?.value.trim();if(i)try{const l=await p("/forms",{method:"POST",body:JSON.stringify({title:i})});c.close(),R.navigate(`/forms/edit/${l.slug}`)}catch(l){E.toast(l.message||"Failed to create form.",{type:"error"})}}n.addEventListener("click",()=>c.close()),o.addEventListener("click",a),s?.addEventListener("keydown",i=>{i.key==="Enter"&&a()})}),d.find("#test-email-btn").off("click").on("click",async()=>{const e=(window.__CMS_SITE__?.smtp||{}).fromAddress||"",r=E.slideover({title:"Send Test Email",size:"sm",position:"right"}),t=document.createElement("div");t.style.cssText="padding:.25rem 0 .5rem;";const n=document.createElement("div");n.className="mb-3";const o=document.createElement("label");o.className="form-label",o.textContent="Send to";const s=document.createElement("input");s.type="email",s.className="form-input",s.value=e,s.placeholder="test@example.com",n.appendChild(o),n.appendChild(s),t.appendChild(n);const a=document.createElement("p");a.className="text-muted",a.style.cssText="font-size:.8rem;margin-bottom:.75rem;",a.textContent="SMTP settings are configured in Site Settings.",t.appendChild(a);const i=document.createElement("div");i.style.cssText="display:flex;justify-content:flex-end;gap:.5rem;margin-top:.75rem;";const l=document.createElement("button");l.className="btn btn-ghost",l.textContent="Cancel";const m=document.createElement("button");m.className="btn btn-primary",m.textContent="Send",i.appendChild(l),i.appendChild(m),t.appendChild(i),r.element.appendChild(t),r.open(),setTimeout(()=>s?.focus(),50),l.addEventListener("click",()=>r.close()),m.addEventListener("click",async()=>{const f=s.value.trim();if(f){m.disabled=!0;try{await p("/forms/test-email",{method:"POST",body:JSON.stringify({to:f})}),r.close(),E.toast("Test email sent.",{type:"success"})}catch(y){E.toast(y.message||"Failed to send test email.",{type:"error"})}finally{m.disabled=!1}}})}),Domma.icons.scan()}};async function g(d){const c=E.loader(d.get(0),{type:"dots"});let e=[];try{e=await p("/forms")}catch{E.toast("Could not load forms.",{type:"error"})}finally{c.destroy()}const r=h();r&&(e=b(e,r)),T.create("#forms-table",{data:e,columns:[{key:"title",title:"Title",render:(t,n)=>{const o=document.createElement("span");o.style.cssText="display:inline-flex;align-items:center;gap:.4rem;flex-wrap:wrap;";const s=document.createElement("a");if(s.href=`#/forms/edit/${u(n.slug)}`,s.textContent=t,s.style.fontWeight="600",o.appendChild(s),n.plugin){const a=document.createElement("span");a.className="badge badge-outline",a.textContent=n.plugin,a.title=`Managed by the ${n.plugin} plugin`,a.style.cssText="font-size:0.65rem;padding:1px 6px;color:var(--dm-warning,#d97706);border-color:var(--dm-warning,#d97706);flex-shrink:0;",o.appendChild(a)}return o.outerHTML}},{key:"slug",title:"Slug",render:t=>{const n=document.createElement("code");return n.textContent=t,n.outerHTML}},{key:"fields",title:"Field Count",render:t=>String(t?.length??0)},{key:"submissionCount",title:"Submission Count",render:t=>String(t??0)},{key:"slug",title:"Actions",render:(t,n)=>{const o=document.createElement("div");o.style.cssText="display:flex;gap:.4rem;justify-content:flex-end;";const s=document.createElement("a");s.href=`#/forms/edit/${u(t)}`,s.className="btn btn-sm btn-ghost",s.setAttribute("data-tooltip","Edit"),s.innerHTML='<span data-icon="edit" data-icon-size="14"></span>';const a=document.createElement("a");a.href=`#/forms/${u(t)}/submissions`,a.className="btn btn-sm btn-ghost",a.setAttribute("data-tooltip","Submissions"),a.innerHTML='<span data-icon="inbox" data-icon-size="14"></span>';const i=document.createElement("button");i.className="btn btn-sm btn-ghost js-clone-form",i.dataset.slug=t,i.setAttribute("data-tooltip","Duplicate"),i.innerHTML='<span data-icon="copy" data-icon-size="14"></span>';const l=document.createElement("button");return l.className="btn btn-sm btn-ghost js-delete-form",l.dataset.slug=t,l.dataset.plugin=n.plugin||"",l.setAttribute("data-tooltip","Delete"),l.innerHTML='<span data-icon="trash" data-icon-size="14"></span>',o.appendChild(s),o.appendChild(a),o.appendChild(i),o.appendChild(l),o.outerHTML}}],emptyMessage:'No forms yet. Click "Create Form" to get started.'}),Domma.icons.scan(),document.querySelectorAll("#forms-table [data-tooltip]").forEach(t=>{E.tooltip(t,{content:t.getAttribute("data-tooltip"),position:"top"})})}
@@ -1,4 +1,4 @@
1
- import{api as y,getUser as C}from"../api.js";import{getProjectFromHash as N}from"../lib/project-context.js";import{keepTableIconsAlive as k}from"../lib/table-icons.js";const $=[{value:"badge-danger",label:"Red (Admin)"},{value:"badge-warning",label:"Orange (Manager)"},{value:"badge-info",label:"Blue (Editor)"},{value:"badge-secondary",label:"Grey (Subscriber)"},{value:"badge-success",label:"Green"},{value:"badge-primary",label:"Primary"}];function w(t){if(!t||!t.length)return"<em>None</em>";const f=new Set(t.map(c=>c.split(".")[0])),b=t.every(c=>!c.includes(".")),o=f.size;return`${o} resource${o!==1?"s":""} ${b?"(full)":"(partial)"}`}function x(t){return String(t).replace(/"/g,"&quot;")}export const rolesView={templateUrl:"/admin/js/templates/roles.html",async onMount(t){if(!C()){R.navigate("/");return}const b=E.loader(t.get(0),{type:"dots"});let o=[];const c=N(),v=async()=>{o=(await y.get("/collections/roles/entries?limit=100")).entries||[],c&&(o=o.filter(a=>(a?.data?.meta?.project||"core")===c))},h=()=>{T.create("#roles-table",{data:o,columns:[{key:"data",title:"Label",render:e=>`<span class="badge ${e.badgeClass||"badge-secondary"}">${e.label||""}</span>`},{key:"data",title:"Name",render:e=>`<code>${e.name||""}</code>`},{key:"data",title:"Level",render:e=>e.level??""},{key:"data",title:"Permissions",render:e=>w(e.permissions)},{key:"id",title:"Actions",render:(e,a)=>{const n=a.data?.level===0;return`
1
+ import{api as y,getUser as C}from"../api.js";import{getProjectFromHash as N}from"../lib/project-context.js";import{keepTableIconsAlive as k}from"../lib/table-icons.js";const $=[{value:"badge-danger",label:"Red (Admin)"},{value:"badge-warning",label:"Orange (Manager)"},{value:"badge-info",label:"Blue (Editor)"},{value:"badge-secondary",label:"Grey (Subscriber)"},{value:"badge-success",label:"Green"},{value:"badge-primary",label:"Primary"}];function w(t){if(!t||!t.length)return"<em>None</em>";const f=new Set(t.map(i=>i.split(".")[0])),b=t.every(i=>!i.includes(".")),o=f.size;return`${o} resource${o!==1?"s":""} ${b?"(full)":"(partial)"}`}function x(t){return String(t).replace(/"/g,"&quot;")}export const rolesView={templateUrl:"/admin/js/templates/roles.html",async onMount(t){if(!C()){R.navigate("/");return}const b=E.loader(t.get(0),{type:"dots"});let o=[];const i=N(),v=async()=>{o=(await y.get("/collections/roles/entries?limit=100")).entries||[],i&&(o=o.filter(a=>(a?.data?.meta?.project||"core")===i))},h=()=>{T.create("#roles-table",{data:o,columns:[{key:"data",title:"Label",render:e=>`<span class="badge ${e.badgeClass||"badge-secondary"}">${e.label||""}</span>`},{key:"data",title:"Name",render:e=>`<code>${e.name||""}</code>`},{key:"data",title:"Level",render:e=>e.level??""},{key:"data",title:"Permissions",render:e=>w(e.permissions)},{key:"id",title:"Actions",render:(e,a)=>{const n=a.data?.level===0;return`
2
2
  <a href="#/roles/edit/${e}" class="btn btn-sm btn-ghost" data-tooltip="Edit"><span data-icon="edit" data-icon-size="14"></span></a>
3
3
  ${n?"":`<button class="btn btn-sm btn-ghost btn-delete-role" data-id="${e}" data-name="${x(a.data?.label)}" data-tooltip="Delete"><span data-icon="trash" data-icon-size="14"></span></button>`}
4
- `}}],emptyMessage:"No roles found."}),Domma.icons.scan(),document.querySelectorAll("#roles-table [data-tooltip]").forEach(e=>{E.tooltip(e,{content:e.getAttribute("data-tooltip"),position:"top"})})};try{await v()}finally{b.destroy()}h(),k(t.get(0)),document.addEventListener("click",async function e(a){if(!document.contains(t.get(0))){document.removeEventListener("click",e);return}const n=a.target.closest(".btn-delete-role");if(!n)return;const m=n.dataset.id,p=n.dataset.name;if(await E.confirm(`Delete role <strong>${p}</strong>? This cannot be undone.`))try{await y.delete(`/collections/roles/entries/${m}`),E.toast("Role deleted.",{type:"success"}),await v(),h()}catch(u){E.toast(`Failed: ${u.message}`,{type:"error"})}}),t.find("#btn-add-role").on("click",()=>{const e=document.createElement("div");e.style.cssText="display:flex;flex-direction:column;gap:1rem;padding:1rem;";const a=(l,i)=>{const s=document.createElement("div"),r=document.createElement("label");return r.textContent=l,r.style.cssText="display:block;font-size:.85rem;margin-bottom:.25rem;font-weight:600;",s.appendChild(r),s.appendChild(i),s},n=Object.assign(document.createElement("input"),{type:"text",className:"form-input",placeholder:"e.g. moderator"}),m=Object.assign(document.createElement("input"),{type:"text",className:"form-input",placeholder:"e.g. Moderator"}),p=Object.assign(document.createElement("input"),{type:"number",className:"form-input",value:"5",min:"1"}),d=document.createElement("select");d.className="form-input",$.forEach(l=>{const i=Object.assign(document.createElement("option"),{value:l.value,textContent:l.label});d.appendChild(i)});const u=Object.assign(document.createElement("button"),{className:"btn btn-primary",textContent:"Create Role"});e.appendChild(a("Name (slug)",n)),e.appendChild(a("Display Label",m)),e.appendChild(a("Level",p)),e.appendChild(a("Badge Colour",d)),e.appendChild(u);const g=E.modal({title:"Add Role",size:"sm"});g.element.appendChild(e),g.open(),u.addEventListener("click",async()=>{const l=n.value.trim().toLowerCase().replace(/[^a-z0-9-]/g,"-"),i=m.value.trim(),s=parseInt(p.value,10);if(!l||!i||isNaN(s)||s<1){E.toast("Name, label and level (\u2265 1) are required.",{type:"error"});return}try{const r=await y.post("/collections/roles/entries",{data:{name:l,label:i,level:s,permissions:[],badgeClass:d.value}});g.close(),E.toast("Role created. Set permissions in the editor.",{type:"success"}),R.navigate(`/roles/edit/${r.id}`)}catch(r){E.toast(`Error: ${r.message}`,{type:"error"})}})})}};
4
+ `}}],emptyMessage:"No roles found."}),Domma.icons.scan(),document.querySelectorAll("#roles-table [data-tooltip]").forEach(e=>{E.tooltip(e,{content:e.getAttribute("data-tooltip"),position:"top"})})};try{await v()}finally{b.destroy()}h(),k(t.get(0)),document.addEventListener("click",async function e(a){if(!document.contains(t.get(0))){document.removeEventListener("click",e);return}const n=a.target.closest(".btn-delete-role");if(!n)return;const m=n.dataset.id,p=n.dataset.name;if(await E.confirm(`Delete role <strong>${p}</strong>? This cannot be undone.`))try{await y.delete(`/collections/roles/entries/${m}`),E.toast("Role deleted.",{type:"success"}),await v(),h()}catch(u){E.toast(`Failed: ${u.message}`,{type:"error"})}}),t.find("#btn-add-role").on("click",()=>{const e=document.createElement("div");e.style.cssText="display:flex;flex-direction:column;gap:1rem;padding:1rem;";const a=(l,c)=>{const s=document.createElement("div"),r=document.createElement("label");return r.textContent=l,r.style.cssText="display:block;font-size:.85rem;margin-bottom:.25rem;font-weight:600;",s.appendChild(r),s.appendChild(c),s},n=Object.assign(document.createElement("input"),{type:"text",className:"form-input",placeholder:"e.g. moderator"}),m=Object.assign(document.createElement("input"),{type:"text",className:"form-input",placeholder:"e.g. Moderator"}),p=Object.assign(document.createElement("input"),{type:"number",className:"form-input",value:"5",min:"1"}),d=document.createElement("select");d.className="form-input",$.forEach(l=>{const c=Object.assign(document.createElement("option"),{value:l.value,textContent:l.label});d.appendChild(c)});const u=Object.assign(document.createElement("button"),{className:"btn btn-primary",textContent:"Create Role"});e.appendChild(a("Name (slug)",n)),e.appendChild(a("Display Label",m)),e.appendChild(a("Level",p)),e.appendChild(a("Badge Colour",d)),e.appendChild(u);const g=E.slideover({title:"Add Role",size:"sm",position:"right"});g.element.appendChild(e),g.open(),u.addEventListener("click",async()=>{const l=n.value.trim().toLowerCase().replace(/[^a-z0-9-]/g,"-"),c=m.value.trim(),s=parseInt(p.value,10);if(!l||!c||isNaN(s)||s<1){E.toast("Name, label and level (\u2265 1) are required.",{type:"error"});return}try{const r=await y.post("/collections/roles/entries",{data:{name:l,label:c,level:s,permissions:[],badgeClass:d.value}});g.close(),E.toast("Role created. Set permissions in the editor.",{type:"success"}),R.navigate(`/roles/edit/${r.id}`)}catch(r){E.toast(`Error: ${r.message}`,{type:"error"})}})})}};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "domma-cms",
3
- "version": "0.36.13",
3
+ "version": "0.36.14",
4
4
  "description": "File-based CMS powered by Domma and Fastify. Run npx domma-cms my-site to create a new project.",
5
5
  "type": "module",
6
6
  "main": "server/server.js",