domma-cms 0.36.13 → 0.36.15

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"})})}
@@ -84,7 +84,7 @@ In active development.
84
84
  Markdown content here.
85
85
  [/slideover]`,"Nested [card] and [grid] shortcodes work inside the slideover body.")),se.appendChild(r);const L=document.createElement("div");L.appendChild(X("DConfig \u2014 Declarative Behaviour")),L.appendChild(W("Define click handlers and class toggles without writing JavaScript. Use the DConfig section above or embed inline with [dconfig]...[/dconfig] in the content body. Inline shortcodes win on selector conflict.")),L.appendChild(A("Toggle a class on click",'{ "#my-btn": { "events": { "click": { "target": "#panel", "toggleClass": "hidden" } } } }','Selector keys use standard CSS selectors. "target" is optional \u2014 defaults to the element itself.')),L.appendChild(A("Inline shortcode syntax",`[dconfig]
86
86
  { "#my-btn": { "events": { "click": { "target": "#panel", "toggleClass": "hidden" } } } }
87
- [/dconfig]`,null)),we.appendChild(L);const B=document.createElement("div");B.appendChild(X("CSS Editor")),B.appendChild(W("The toolbar's </> button (left of the Split View icon) swaps the left pane between Markdown and Custom CSS. Edits are saved together with the page \u2014 one Save, one toast."));const N=document.createElement("table");N.style.cssText="width:100%;border-collapse:collapse;font-size:.85em;margin-bottom:.5rem;",[["</> button","Toggle between Markdown editor and Custom CSS editor"],["Split / Write / Preview","Standard view mode buttons to the right of </>"],["Save","Saves both page content and CSS in a single operation"]].forEach(([h,g])=>{const i=document.createElement("tr"),o=document.createElement("td");o.style.cssText="padding:.3rem .4rem;border-bottom:1px solid var(--dm-border,#333);vertical-align:top;",o.appendChild(U(h));const s=document.createElement("td");s.style.cssText="padding:.3rem .4rem;border-bottom:1px solid var(--dm-border,#333);vertical-align:top;color:var(--dm-text-muted,#aaa);",s.textContent=g,i.appendChild(o),i.appendChild(s),N.appendChild(i)}),B.appendChild(N),we.appendChild(B);const w=document.createElement("div");w.appendChild(X("Colour Picker")),w.appendChild(W("Insert \u2192 Colour Picker opens a centred colour picker. Pick a colour, then use Copy to copy the hex code to the clipboard or Insert to drop it at the cursor. Click outside the panel to dismiss.")),we.appendChild(w);const z=document.createElement("div");z.appendChild(X("Keyboard Shortcuts"));const b=document.createElement("table");b.style.cssText="width:100%;border-collapse:collapse;font-size:.85em;margin-bottom:.5rem;",[["Ctrl + B","Bold selected text"],["Ctrl + I","Italic selected text"],["Ctrl + K","Insert link dialog"],["Tab","Indent 2 spaces at cursor"],["Shift + Tab","Dedent 2 spaces on current line"],["Ctrl + X","Cut current line (no selection) or cut selection"],["Ctrl + C","Copy current line (no selection) or copy selection"],["Ctrl + V","Paste from clipboard"],["Ctrl + Z","Undo"],["Ctrl + Y","Redo"]].forEach(([h,g])=>{const i=document.createElement("tr"),o=document.createElement("td");o.style.cssText="padding:.3rem .4rem;border-bottom:1px solid var(--dm-border,#333);vertical-align:top;white-space:nowrap;",o.appendChild(U(h));const s=document.createElement("td");s.style.cssText="padding:.3rem .4rem;border-bottom:1px solid var(--dm-border,#333);vertical-align:top;color:var(--dm-text-muted,#aaa);",s.textContent=g,i.appendChild(o),i.appendChild(s),b.appendChild(i)}),z.appendChild(b),Be.appendChild(z);const k=document.createElement("div");k.appendChild(X("Tips"));const M=document.createElement("table");M.style.cssText="width:100%;border-collapse:collapse;font-size:.85em;margin-bottom:.5rem;",[['class="text-center"',"Centre text and inline elements on any shortcode"],['class="mx-auto"',"Centre a block element horizontally"],["[center]...[/center]","Shorthand wrapper for centred content"]].forEach(([h,g])=>{const i=document.createElement("tr"),o=document.createElement("td");o.style.cssText="padding:.3rem .4rem;border-bottom:1px solid var(--dm-border,#333);vertical-align:top;",o.appendChild(U(h));const s=document.createElement("td");s.style.cssText="padding:.3rem .4rem;border-bottom:1px solid var(--dm-border,#333);vertical-align:top;color:var(--dm-text-muted,#aaa);",s.textContent=g,i.appendChild(o),i.appendChild(s),M.appendChild(i)}),k.appendChild(M),k.appendChild(W("Most shortcodes accept a class attribute for custom styling.")),Be.appendChild(k),d.setContent($e),d.open(),E.tabs(Ae),I.scan($e)}function st(d,ce){const de=d.find("#history-tab").get(0),U=d.find("#version-list").get(0),X=d.find("#version-compare").get(0),$e=d.find("#version-compare-old").get(0),Ae=d.find("#version-compare-new").get(0),Me=d.find("#version-compare-label").get(0),_=d.find("#version-compare-back-btn").get(0),Ie=d.find("#version-restore-btn").get(0),Be=d.find("#history-save-named-btn").get(0),ve=d.find("#history-delete-selected-btn").get(0);let se=!1,Ce=null;function we(){U.style.display="",X.style.display="none",Ce=null}function qe(){U.style.display="none",X.style.display=""}async function pe(){U.textContent="";const ee=document.createElement("p");ee.style.color="var(--dm-text-muted)",ee.textContent="Loading\u2026",U.appendChild(ee);let le;try{le=(await Y.versions.list(ce)).versions||[]}catch(V){U.textContent="";const q=document.createElement("p");q.style.color="var(--dm-danger,#e55)",q.textContent="Failed to load versions: "+V.message,U.appendChild(q);return}if(U.textContent="",ve.style.display="none",!le.length){const V=document.createElement("p");V.style.color="var(--dm-text-muted)",V.textContent="No versions saved yet. Versions are created automatically on each save.",U.appendChild(V);return}const ge=document.createElement("table");ge.style.cssText="width:100%;border-collapse:collapse;font-size:.875rem;";const ae=document.createElement("thead"),W=document.createElement("tr"),A=document.createElement("th");A.style.cssText="padding:.5rem .75rem;width:2rem;border-bottom:1px solid var(--dm-border);";const oe=document.createElement("input");oe.type="checkbox",oe.setAttribute("aria-label","Select all versions"),A.appendChild(oe),W.appendChild(A),["Date","Author","Type","Label","Actions"].forEach(V=>{const q=document.createElement("th");q.style.cssText="text-align:left;padding:.5rem .75rem;border-bottom:1px solid var(--dm-border);font-size:.8rem;color:var(--dm-text-muted);",q.textContent=V,W.appendChild(q)}),ae.appendChild(W),ge.appendChild(ae);const me=[];function ze(){const V=me.some(ue=>ue.checked),q=me.length>0&&me.every(ue=>ue.checked);ve.style.display=V?"":"none",oe.checked=q,oe.indeterminate=V&&!q}oe.addEventListener("change",()=>{me.forEach(V=>{V.checked=oe.checked}),ze()});const De=document.createElement("tbody");le.forEach(V=>{const q=document.createElement("tr");q.style.borderBottom="1px solid var(--dm-border)";const ue=document.createElement("td");ue.style.padding=".5rem .75rem";const Ee=document.createElement("input");Ee.type="checkbox",Ee.value=V.filename,Ee.addEventListener("change",ze),ue.appendChild(Ee),me.push(Ee);const ke=document.createElement("td");ke.style.padding=".5rem .75rem",ke.textContent=D(V.createdAt).format("D MMM YYYY, HH:mm");const he=document.createElement("td");he.style.padding=".5rem .75rem",he.textContent=V.author||"\u2014";const Te=document.createElement("td");Te.style.padding=".5rem .75rem";const be=document.createElement("span");be.className="badge badge-"+(V.type==="manual"?"primary":"secondary"),be.style.cssText="font-size:.75rem;padding:.2rem .5rem;border-radius:4px;",be.textContent=V.type,Te.appendChild(be);const Ne=document.createElement("td");Ne.style.cssText="padding:.5rem .75rem;color:var(--dm-text-muted);",Ne.textContent=V.label||"\u2014";const fe=document.createElement("td");fe.style.padding=".5rem .75rem";const ne=document.createElement("button");ne.className="btn btn-ghost btn-xs",ne.style.marginRight=".4rem",ne.title="Compare",ne.innerHTML='<span data-icon="columns"></span>',ne.addEventListener("click",async()=>{try{const xe=await Y.versions.get(ce,V.filename);$e.textContent=xe.content,Ae.textContent=d.find("#markdown-editor").get(0).value,Me.textContent=D(V.createdAt).format("D MMM YYYY, HH:mm")+(V.label?` \u2014 ${V.label}`:""),Ce=V.filename,qe()}catch(xe){E.toast("Failed to load version: "+xe.message,{type:"error"})}});const ie=document.createElement("button");ie.className="btn btn-ghost btn-xs",ie.style.color="var(--dm-danger,#e55)",ie.title="Delete",ie.innerHTML='<span data-icon="trash"></span>',ie.addEventListener("click",async()=>{if(await E.confirm("Delete this version? This cannot be undone."))try{await Y.versions.delete(ce,V.filename),E.toast("Version deleted.",{type:"success"}),await pe()}catch(xe){E.toast("Failed to delete: "+xe.message,{type:"error"})}}),fe.appendChild(ne),fe.appendChild(ie),q.appendChild(ue),q.appendChild(ke),q.appendChild(he),q.appendChild(Te),q.appendChild(Ne),q.appendChild(fe),De.appendChild(q)}),ge.appendChild(De),U.appendChild(ge),I.scan(U)}de.addEventListener("click",async()=>{se||(se=!0,await pe())}),_.addEventListener("click",()=>{we()}),Ie.addEventListener("click",async()=>{if(!(!Ce||!await E.confirm("Restore this version? The current page content will be overwritten (a pre-restore snapshot will be saved automatically).")))try{await Y.versions.restore(ce,Ce),E.toast("Page restored. Reloading editor\u2026",{type:"success"}),R.navigate("/pages"),setTimeout(()=>R.navigate(`/pages/edit${ce}`),300)}catch(ee){E.toast("Restore failed: "+ee.message,{type:"error"})}}),ve.addEventListener("click",async()=>{const ee=[...U.querySelectorAll("tbody input[type=checkbox]:checked")].map(le=>le.value);if(!(!ee.length||!await E.confirm(`Delete ${ee.length} version${ee.length>1?"s":""}? This cannot be undone.`)))try{await Y.versions.bulkDelete(ce,ee),E.toast(`${ee.length} version${ee.length>1?"s":""} deleted.`,{type:"success"}),await pe()}catch(le){E.toast("Delete failed: "+le.message,{type:"error"})}}),Be.addEventListener("click",()=>{const ee=E.modal({title:"Save Named Version"}),le=document.createElement("div");le.style.padding="1rem";const ge=document.createElement("label");ge.className="form-label",ge.textContent="Version Label";const ae=document.createElement("input");ae.type="text",ae.className="form-input",ae.placeholder="e.g. Initial design, Before restructure",ae.style.marginBottom=".75rem";const W=document.createElement("button");W.className="btn btn-primary",W.textContent="Save Version",W.addEventListener("click",async()=>{const A=ae.value.trim();try{await Y.versions.create(ce,A||null),E.toast("Named version saved.",{type:"success"}),ee.close(),se=!1,de.classList.contains("active")&&(se=!0,await pe())}catch(oe){E.toast("Failed to save version: "+oe.message,{type:"error"})}}),le.appendChild(ge),le.appendChild(ae),le.appendChild(W),ee.element.appendChild(le),ee.open(),setTimeout(()=>ae.focus(),100)})}let Re=!1,Ge=null,je=!1;function rt(d){return d.split(`
87
+ [/dconfig]`,null)),we.appendChild(L);const B=document.createElement("div");B.appendChild(X("CSS Editor")),B.appendChild(W("The toolbar's </> button (left of the Split View icon) swaps the left pane between Markdown and Custom CSS. Edits are saved together with the page \u2014 one Save, one toast."));const N=document.createElement("table");N.style.cssText="width:100%;border-collapse:collapse;font-size:.85em;margin-bottom:.5rem;",[["</> button","Toggle between Markdown editor and Custom CSS editor"],["Split / Write / Preview","Standard view mode buttons to the right of </>"],["Save","Saves both page content and CSS in a single operation"]].forEach(([h,g])=>{const i=document.createElement("tr"),o=document.createElement("td");o.style.cssText="padding:.3rem .4rem;border-bottom:1px solid var(--dm-border,#333);vertical-align:top;",o.appendChild(U(h));const s=document.createElement("td");s.style.cssText="padding:.3rem .4rem;border-bottom:1px solid var(--dm-border,#333);vertical-align:top;color:var(--dm-text-muted,#aaa);",s.textContent=g,i.appendChild(o),i.appendChild(s),N.appendChild(i)}),B.appendChild(N),we.appendChild(B);const w=document.createElement("div");w.appendChild(X("Colour Picker")),w.appendChild(W("Insert \u2192 Colour Picker opens a centred colour picker. Pick a colour, then use Copy to copy the hex code to the clipboard or Insert to drop it at the cursor. Click outside the panel to dismiss.")),we.appendChild(w);const z=document.createElement("div");z.appendChild(X("Keyboard Shortcuts"));const b=document.createElement("table");b.style.cssText="width:100%;border-collapse:collapse;font-size:.85em;margin-bottom:.5rem;",[["Ctrl + B","Bold selected text"],["Ctrl + I","Italic selected text"],["Ctrl + K","Insert link dialog"],["Tab","Indent 2 spaces at cursor"],["Shift + Tab","Dedent 2 spaces on current line"],["Ctrl + X","Cut current line (no selection) or cut selection"],["Ctrl + C","Copy current line (no selection) or copy selection"],["Ctrl + V","Paste from clipboard"],["Ctrl + Z","Undo"],["Ctrl + Y","Redo"]].forEach(([h,g])=>{const i=document.createElement("tr"),o=document.createElement("td");o.style.cssText="padding:.3rem .4rem;border-bottom:1px solid var(--dm-border,#333);vertical-align:top;white-space:nowrap;",o.appendChild(U(h));const s=document.createElement("td");s.style.cssText="padding:.3rem .4rem;border-bottom:1px solid var(--dm-border,#333);vertical-align:top;color:var(--dm-text-muted,#aaa);",s.textContent=g,i.appendChild(o),i.appendChild(s),b.appendChild(i)}),z.appendChild(b),Be.appendChild(z);const k=document.createElement("div");k.appendChild(X("Tips"));const M=document.createElement("table");M.style.cssText="width:100%;border-collapse:collapse;font-size:.85em;margin-bottom:.5rem;",[['class="text-center"',"Centre text and inline elements on any shortcode"],['class="mx-auto"',"Centre a block element horizontally"],["[center]...[/center]","Shorthand wrapper for centred content"]].forEach(([h,g])=>{const i=document.createElement("tr"),o=document.createElement("td");o.style.cssText="padding:.3rem .4rem;border-bottom:1px solid var(--dm-border,#333);vertical-align:top;",o.appendChild(U(h));const s=document.createElement("td");s.style.cssText="padding:.3rem .4rem;border-bottom:1px solid var(--dm-border,#333);vertical-align:top;color:var(--dm-text-muted,#aaa);",s.textContent=g,i.appendChild(o),i.appendChild(s),M.appendChild(i)}),k.appendChild(M),k.appendChild(W("Most shortcodes accept a class attribute for custom styling.")),Be.appendChild(k),d.setContent($e),d.open(),E.tabs(Ae),I.scan($e)}function st(d,ce){const de=d.find("#history-tab").get(0),U=d.find("#version-list").get(0),X=d.find("#version-compare").get(0),$e=d.find("#version-compare-old").get(0),Ae=d.find("#version-compare-new").get(0),Me=d.find("#version-compare-label").get(0),_=d.find("#version-compare-back-btn").get(0),Ie=d.find("#version-restore-btn").get(0),Be=d.find("#history-save-named-btn").get(0),ve=d.find("#history-delete-selected-btn").get(0);let se=!1,Ce=null;function we(){U.style.display="",X.style.display="none",Ce=null}function qe(){U.style.display="none",X.style.display=""}async function pe(){U.textContent="";const ee=document.createElement("p");ee.style.color="var(--dm-text-muted)",ee.textContent="Loading\u2026",U.appendChild(ee);let le;try{le=(await Y.versions.list(ce)).versions||[]}catch(V){U.textContent="";const q=document.createElement("p");q.style.color="var(--dm-danger,#e55)",q.textContent="Failed to load versions: "+V.message,U.appendChild(q);return}if(U.textContent="",ve.style.display="none",!le.length){const V=document.createElement("p");V.style.color="var(--dm-text-muted)",V.textContent="No versions saved yet. Versions are created automatically on each save.",U.appendChild(V);return}const ge=document.createElement("table");ge.style.cssText="width:100%;border-collapse:collapse;font-size:.875rem;";const ae=document.createElement("thead"),W=document.createElement("tr"),A=document.createElement("th");A.style.cssText="padding:.5rem .75rem;width:2rem;border-bottom:1px solid var(--dm-border);";const oe=document.createElement("input");oe.type="checkbox",oe.setAttribute("aria-label","Select all versions"),A.appendChild(oe),W.appendChild(A),["Date","Author","Type","Label","Actions"].forEach(V=>{const q=document.createElement("th");q.style.cssText="text-align:left;padding:.5rem .75rem;border-bottom:1px solid var(--dm-border);font-size:.8rem;color:var(--dm-text-muted);",q.textContent=V,W.appendChild(q)}),ae.appendChild(W),ge.appendChild(ae);const me=[];function ze(){const V=me.some(ue=>ue.checked),q=me.length>0&&me.every(ue=>ue.checked);ve.style.display=V?"":"none",oe.checked=q,oe.indeterminate=V&&!q}oe.addEventListener("change",()=>{me.forEach(V=>{V.checked=oe.checked}),ze()});const De=document.createElement("tbody");le.forEach(V=>{const q=document.createElement("tr");q.style.borderBottom="1px solid var(--dm-border)";const ue=document.createElement("td");ue.style.padding=".5rem .75rem";const Ee=document.createElement("input");Ee.type="checkbox",Ee.value=V.filename,Ee.addEventListener("change",ze),ue.appendChild(Ee),me.push(Ee);const ke=document.createElement("td");ke.style.padding=".5rem .75rem",ke.textContent=D(V.createdAt).format("D MMM YYYY, HH:mm");const he=document.createElement("td");he.style.padding=".5rem .75rem",he.textContent=V.author||"\u2014";const Te=document.createElement("td");Te.style.padding=".5rem .75rem";const be=document.createElement("span");be.className="badge badge-"+(V.type==="manual"?"primary":"secondary"),be.style.cssText="font-size:.75rem;padding:.2rem .5rem;border-radius:4px;",be.textContent=V.type,Te.appendChild(be);const Ne=document.createElement("td");Ne.style.cssText="padding:.5rem .75rem;color:var(--dm-text-muted);",Ne.textContent=V.label||"\u2014";const fe=document.createElement("td");fe.style.padding=".5rem .75rem";const ne=document.createElement("button");ne.className="btn btn-ghost btn-xs",ne.style.marginRight=".4rem",ne.title="Compare",ne.innerHTML='<span data-icon="columns"></span>',ne.addEventListener("click",async()=>{try{const xe=await Y.versions.get(ce,V.filename);$e.textContent=xe.content,Ae.textContent=d.find("#markdown-editor").get(0).value,Me.textContent=D(V.createdAt).format("D MMM YYYY, HH:mm")+(V.label?` \u2014 ${V.label}`:""),Ce=V.filename,qe()}catch(xe){E.toast("Failed to load version: "+xe.message,{type:"error"})}});const ie=document.createElement("button");ie.className="btn btn-ghost btn-xs",ie.style.color="var(--dm-danger,#e55)",ie.title="Delete",ie.innerHTML='<span data-icon="trash"></span>',ie.addEventListener("click",async()=>{if(await E.confirm("Delete this version? This cannot be undone."))try{await Y.versions.delete(ce,V.filename),E.toast("Version deleted.",{type:"success"}),await pe()}catch(xe){E.toast("Failed to delete: "+xe.message,{type:"error"})}}),fe.appendChild(ne),fe.appendChild(ie),q.appendChild(ue),q.appendChild(ke),q.appendChild(he),q.appendChild(Te),q.appendChild(Ne),q.appendChild(fe),De.appendChild(q)}),ge.appendChild(De),U.appendChild(ge),I.scan(U)}de.addEventListener("click",async()=>{se||(se=!0,await pe())}),_.addEventListener("click",()=>{we()}),Ie.addEventListener("click",async()=>{if(!(!Ce||!await E.confirm("Restore this version? The current page content will be overwritten (a pre-restore snapshot will be saved automatically).")))try{await Y.versions.restore(ce,Ce),E.toast("Page restored. Reloading editor\u2026",{type:"success"}),R.navigate("/pages"),setTimeout(()=>R.navigate(`/pages/edit${ce}`),300)}catch(ee){E.toast("Restore failed: "+ee.message,{type:"error"})}}),ve.addEventListener("click",async()=>{const ee=[...U.querySelectorAll("tbody input[type=checkbox]:checked")].map(le=>le.value);if(!(!ee.length||!await E.confirm(`Delete ${ee.length} version${ee.length>1?"s":""}? This cannot be undone.`)))try{await Y.versions.bulkDelete(ce,ee),E.toast(`${ee.length} version${ee.length>1?"s":""} deleted.`,{type:"success"}),await pe()}catch(le){E.toast("Delete failed: "+le.message,{type:"error"})}}),Be.addEventListener("click",()=>{const ee=E.slideover({title:"Save Named Version",size:"sm",position:"right"}),le=document.createElement("div");le.style.padding="1rem";const ge=document.createElement("label");ge.className="form-label",ge.textContent="Version Label";const ae=document.createElement("input");ae.type="text",ae.className="form-input",ae.placeholder="e.g. Initial design, Before restructure",ae.style.marginBottom=".75rem";const W=document.createElement("button");W.className="btn btn-primary",W.textContent="Save Version",W.addEventListener("click",async()=>{const A=ae.value.trim();try{await Y.versions.create(ce,A||null),E.toast("Named version saved.",{type:"success"}),ee.close(),se=!1,de.classList.contains("active")&&(se=!0,await pe())}catch(oe){E.toast("Failed to save version: "+oe.message,{type:"error"})}}),le.appendChild(ge),le.appendChild(ae),le.appendChild(W),ee.element.appendChild(le),ee.open(),setTimeout(()=>ae.focus(),100)})}let Re=!1,Ge=null,je=!1;function rt(d){return d.split(`
88
88
  `).map(ce=>ce.trimEnd()).join(`
89
89
  `).replace(/\n{3,}/g,`
90
90
 
@@ -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.15",
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",