domma-cms 0.36.10 → 0.36.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/admin/js/lib/table-icons.js +1 -0
- package/admin/js/views/actions-list.js +1 -1
- package/admin/js/views/api-endpoints.js +5 -5
- package/admin/js/views/api-tokens.js +6 -6
- package/admin/js/views/blocks.js +5 -5
- package/admin/js/views/collection-entries.js +1 -1
- package/admin/js/views/collections.js +1 -1
- package/admin/js/views/components.js +4 -4
- package/admin/js/views/form-submissions.js +1 -1
- package/admin/js/views/forms.js +1 -1
- package/admin/js/views/menus.js +5 -5
- package/admin/js/views/projects.js +3 -3
- package/admin/js/views/roles.js +4 -4
- package/admin/js/views/users.js +4 -4
- package/admin/js/views/views-list.js +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export function keepTableIconsAlive(t){if(!t||typeof MutationObserver>"u")return null;let o=null;const n=()=>e.observe(t,{childList:!0,subtree:!0}),i=()=>{clearTimeout(o),o=setTimeout(()=>{e.disconnect(),window.Domma?.icons?.scan?.(t),t.querySelectorAll("[data-tooltip]").forEach(r=>{typeof E<"u"&&E.tooltip&&E.tooltip(r,{content:r.getAttribute("data-tooltip"),position:"top"})}),n()},50)},e=new MutationObserver(i);return n(),i(),e}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{api as r}from"../api.js";import{filterByProject as p,getProjectFromHash as m}from"../lib/project-context.js";function
|
|
1
|
+
import{api as r}from"../api.js";import{filterByProject as p,getProjectFromHash as m}from"../lib/project-context.js";import{keepTableIconsAlive as u}from"../lib/table-icons.js";function i(e){return String(e).replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""")}export const actionsListView={templateUrl:"/admin/js/templates/actions-list.html",async onMount(e){await d(e),u(e.get(0)),e.off("click",".js-delete-action").on("click",".js-delete-action",async function(){const a=this.dataset.slug;if(await E.confirm(`Delete action "${a}"? This cannot be undone.`))try{await r.actions.delete(a),E.toast("Action deleted.",{type:"success"}),await d(e)}catch{E.toast("Failed to delete action.",{type:"error"})}}),e.find("#create-action-btn").off("click").on("click",()=>{R.navigate("/actions/new")}),Domma.icons.scan()}};async function d(e){let a=[];const c=E.loader(e.get(0),{type:"dots"});try{a=await r.actions.list(),e.find("#actions-pro-notice").hide()}catch(t){t.message?.includes("MongoDB")||t.message?.includes("pro mode")||t.message?.includes("connection")?e.find("#actions-pro-notice").show():E.toast("Could not load actions.",{type:"error"})}finally{c.destroy()}const l=m();l&&(a=p(a,l)),T.create("#actions-table",{data:a,columns:[{key:"title",title:"Title",render:(t,n)=>{const o=document.createElement("a");return o.href=`#/actions/edit/${i(n.slug)}`,o.textContent=t,o.style.fontWeight="600",o.outerHTML}},{key:"slug",title:"Slug",render:t=>`<code>${i(t)}</code>`},{key:"collection",title:"Collection",render:t=>`<code>${i(t||"\u2014")}</code>`},{key:"trigger",title:"Trigger",render:t=>i(t?.label||t?.type||"manual")},{key:"steps",title:"Steps",render:t=>String(Array.isArray(t)?t.length:0)},{key:"access",title:"Roles",render:t=>(t?.roles||[]).map(n=>`<span class="badge badge-secondary">${i(n)}</span>`).join(" ")},{key:"slug",title:"Actions",render:t=>{const n=document.createElement("div");n.style.cssText="display:flex;gap:.4rem;justify-content:flex-end;";const o=document.createElement("a");o.href=`#/actions/edit/${i(t)}`,o.className="btn btn-sm btn-ghost",o.setAttribute("data-tooltip","Edit"),o.innerHTML='<span data-icon="edit" data-icon-size="14"></span>';const s=document.createElement("button");return s.className="btn btn-sm btn-danger js-delete-action",s.dataset.slug=t,s.setAttribute("data-tooltip","Delete"),s.innerHTML='<span data-icon="trash" data-icon-size="14"></span>',n.appendChild(o),n.appendChild(s),n.outerHTML}}],emptyMessage:'No actions yet. Click "New Action" to create your first action.'}),Domma.icons.scan(),document.querySelectorAll("#actions-table [data-tooltip]").forEach(t=>{E.tooltip(t,{content:t.getAttribute("data-tooltip"),position:"top"})})}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import{api as
|
|
1
|
+
import{api as s}from"../api.js";import{filterByProject as p,getProjectFromHash as c}from"../lib/project-context.js";import{keepTableIconsAlive as r}from"../lib/table-icons.js";function n(a){return String(a??"").replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""")}function b(a){return a==="public"?'<span class="badge badge-warning">Public</span>':a==="token"?'<span class="badge badge-info">Token</span>':`<span class="badge badge-secondary">${n(a)}</span>`}export const apiEndpointsView={templateUrl:"/admin/js/templates/api-endpoints.html",async onMount(a){const l=E.loader(a.get(0),{type:"dots"});let i=await s.apiEndpoints.list().catch(()=>[]);l.destroy();const d=c();d&&(i=p(i,d)),T.create("#api-endpoints-table",{data:i,emptyMessage:'No endpoints yet. Click "New endpoint" to build one.',columns:[{key:"name",title:"Name",sortable:!0},{key:"path",title:"URL",render:(e,t)=>`<code>/api/x/${n(t.project)}${n(t.path)}</code>`},{key:"collection",title:"Collection",sortable:!0,render:e=>`<code>${n(e)}</code>`},{key:"mode",title:"Mode",render:e=>n(e)},{key:"auth",title:"Auth",render:e=>b(e)},{key:"enabled",title:"Status",render:e=>e?'<span class="badge badge-success">Enabled</span>':'<span class="badge badge-secondary">Disabled</span>'},{key:"_actions",title:"Actions",render:(e,t)=>`
|
|
2
2
|
<span style="display:inline-flex;gap:0.25rem;">
|
|
3
|
-
<a class="btn btn-sm btn-ghost" href="#/api-endpoints/edit/${encodeURIComponent(t.id)}" data-tooltip="Edit"><span data-icon="edit"></span></a>
|
|
4
|
-
<button class="btn btn-sm btn-ghost btn-toggle-endpoint" data-id="${n(t.id)}" data-enabled="${t.enabled?"1":""}" data-tooltip="${t.enabled?"Disable":"Enable"}"><span data-icon="${t.enabled?"pause":"play"}"></span></button>
|
|
5
|
-
<button class="btn btn-sm btn-danger btn-delete-endpoint" data-id="${n(t.id)}" data-name="${n(t.name)}" data-tooltip="Delete"><span data-icon="trash"></span></button>
|
|
3
|
+
<a class="btn btn-sm btn-ghost" href="#/api-endpoints/edit/${encodeURIComponent(t.id)}" data-tooltip="Edit"><span data-icon="edit" data-icon-size="14"></span></a>
|
|
4
|
+
<button class="btn btn-sm btn-ghost btn-toggle-endpoint" data-id="${n(t.id)}" data-enabled="${t.enabled?"1":""}" data-tooltip="${t.enabled?"Disable":"Enable"}"><span data-icon="${t.enabled?"pause":"play"}" data-icon-size="14"></span></button>
|
|
5
|
+
<button class="btn btn-sm btn-danger btn-delete-endpoint" data-id="${n(t.id)}" data-name="${n(t.name)}" data-tooltip="Delete"><span data-icon="trash" data-icon-size="14"></span></button>
|
|
6
6
|
</span>
|
|
7
|
-
`}]}),Domma.icons.scan(a.get(0)),document.querySelectorAll("#api-endpoints-table [data-tooltip]").forEach(e=>{E.tooltip(e,{content:e.getAttribute("data-tooltip"),position:"top"})}),a.off("click",".btn-toggle-endpoint").on("click",".btn-toggle-endpoint",async function(){const e=$(this).data("id"),t=!!$(this).data("enabled");try{await
|
|
7
|
+
`}]}),Domma.icons.scan(a.get(0)),r(a.get(0)),document.querySelectorAll("#api-endpoints-table [data-tooltip]").forEach(e=>{E.tooltip(e,{content:e.getAttribute("data-tooltip"),position:"top"})}),a.off("click",".btn-toggle-endpoint").on("click",".btn-toggle-endpoint",async function(){const e=$(this).data("id"),t=!!$(this).data("enabled");try{await s.apiEndpoints.update(e,{enabled:!t}),E.toast(`Endpoint ${t?"disabled":"enabled"}.`,{type:"success"}),location.reload()}catch(o){E.toast(`Update failed: ${o.message||o}`,{type:"error"})}}),a.off("click",".btn-delete-endpoint").on("click",".btn-delete-endpoint",async function(){const e=$(this).data("id"),t=$(this).data("name");if(await E.confirm(`Delete endpoint "${t}"? External callers using it will immediately get 404s. This cannot be undone.`))try{await s.apiEndpoints.remove(e),E.toast("Endpoint deleted.",{type:"success"}),location.reload()}catch(o){E.toast(`Delete failed: ${o.message||o}`,{type:"error"})}})}};
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import{api as m}from"../api.js";function d(a){return String(a??"").replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""")}function
|
|
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
|
|
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}=
|
|
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,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""")}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)=>`
|
|
4
4
|
<span style="display:inline-flex;gap:0.25rem;">
|
|
5
|
-
<button class="btn btn-sm btn-ghost btn-toggle-token" data-id="${d(t.id)}" data-enabled="${t.enabled?"1":""}" data-tooltip="${t.enabled?"Disable":"Enable"}"><span data-icon="${t.enabled?"pause":"play"}"></span></button>
|
|
6
|
-
<button class="btn btn-sm btn-danger btn-revoke-token" data-id="${d(t.id)}" data-name="${d(t.name)}" data-tooltip="Revoke"><span data-icon="trash"></span></button>
|
|
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
|
+
<button class="btn btn-sm btn-danger btn-revoke-token" data-id="${d(t.id)}" data-name="${d(t.name)}" data-tooltip="Revoke"><span data-icon="trash" data-icon-size="14"></span></button>
|
|
7
7
|
</span>
|
|
8
|
-
`}]}),Domma.icons.scan(a.get(0)),document.querySelectorAll("#api-tokens-table [data-tooltip]").forEach(e=>{E.tooltip(e,{content:e.getAttribute("data-tooltip"),position:"top"})});const o=()=>location.reload();a.off("click","#btn-new-token").on("click","#btn-new-token",()=>{if(!n.length){E.toast("No projects available \u2014 create a project first.",{type:"error"});return}
|
|
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"})}})}};
|
package/admin/js/views/blocks.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import{api as i}from"../api.js";import{filterByProject as m,getProjectFromHash as u}from"../lib/project-context.js";function d(e){return String(e??"").replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""")}function
|
|
2
|
-
<a href="#/blocks/edit/${s}" class="btn btn-sm btn-ghost" data-tooltip="Edit"><span data-icon="edit"></span></a>
|
|
3
|
-
<button class="btn btn-sm btn-ghost js-export-block" data-name="${s}" data-tooltip="Export as .dmblock.json"><span data-icon="download"></span></button>
|
|
4
|
-
<button class="btn btn-sm btn-danger js-delete-block" data-name="${s}" data-tooltip="Delete"><span data-icon="trash"></span></button>
|
|
5
|
-
</div>`}}]}),
|
|
1
|
+
import{api as i}from"../api.js";import{filterByProject as m,getProjectFromHash as u}from"../lib/project-context.js";import{keepTableIconsAlive as f}from"../lib/table-icons.js";function d(e){return String(e??"").replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""")}function k(e){for(;e.firstChild;)e.removeChild(e.firstChild)}export const blocksView={templateUrl:"/admin/js/templates/blocks.html",async onMount(e){b(),await l(e),h(e),Domma.icons.scan(e.get(0)),f(e.get(0))}};let p=!1;function b(){p||(p=!0,I.register("download",{viewBox:"0 0 24 24",paths:["M12 3v12","M7 10l5 5 5-5","M5 19h14"],stroke:"currentColor",fill:"none",strokeWidth:2,strokeLinecap:"round",strokeLinejoin:"round"}),I.register("upload",{viewBox:"0 0 24 24",paths:["M12 21V9","M7 14l5-5 5 5","M5 5h14"],stroke:"currentColor",fill:"none",strokeWidth:2,strokeLinecap:"round",strokeLinejoin:"round"}),I.register("plus",{viewBox:"0 0 24 24",paths:["M12 5v14","M5 12h14"],stroke:"currentColor",fill:"none",strokeWidth:2,strokeLinecap:"round",strokeLinejoin:"round"}))}async function l(e){const o=e.find("#blocks-table-container").get(0);if(!o)return;let r=[];const n=E.loader(e.get(0),{type:"dots"});try{r=await i.blocks.list()}catch(t){k(o);const s=document.createElement("p");s.className="text-muted",s.textContent=`Failed to load blocks: ${t.message}`,o.appendChild(s);return}finally{n.destroy()}const a=u();a&&(r=m(r,a)),T.create(o,{data:r,emptyMessage:'No blocks yet. Click "New Block" to create your first template.',columns:[{key:"name",title:"Name",render:t=>`<a href="#/blocks/edit/${d(t)}">${d(t)}.html</a>`},{key:"size",title:"Size",render:t=>`${t} B`},{key:"updatedAt",title:"Updated",render:t=>t?new Date(t).toLocaleString():"\u2014"},{key:"name",title:"Actions",render:t=>{const s=d(t);return`<div style="display:flex;gap:.4rem;justify-content:flex-end;">
|
|
2
|
+
<a href="#/blocks/edit/${s}" class="btn btn-sm btn-ghost" data-tooltip="Edit"><span data-icon="edit" data-icon-size="14"></span></a>
|
|
3
|
+
<button class="btn btn-sm btn-ghost js-export-block" data-name="${s}" data-tooltip="Export as .dmblock.json"><span data-icon="download" data-icon-size="14"></span></button>
|
|
4
|
+
<button class="btn btn-sm btn-danger js-delete-block" data-name="${s}" data-tooltip="Delete"><span data-icon="trash" data-icon-size="14"></span></button>
|
|
5
|
+
</div>`}}]}),e.off("click",".js-delete-block").on("click",".js-delete-block",async function(){await y(this.dataset.name,e)}),e.off("click",".js-export-block").on("click",".js-export-block",async function(){try{await i.blocks.exportBundle(this.dataset.name)}catch(t){E.toast(t.message||"Export failed.",{type:"error"})}}),Domma.icons.scan(o),o.querySelectorAll("[data-tooltip]").forEach(t=>{E.tooltip(t,{content:t.getAttribute("data-tooltip"),position:"top"})})}async function y(e,o){if(await E.confirm(`Delete block "${e}"? This cannot be undone.`))try{await i.blocks.delete(e),E.toast("Block deleted.",{type:"success"}),await l(o)}catch(n){E.toast(n.message||"Failed to delete block.",{type:"error"})}}function h(e){const o=e.find("#import-block-btn").get(0),r=e.find("#import-block-file").get(0);!o||!r||(o.addEventListener("click",()=>r.click()),r.addEventListener("change",async()=>{const n=r.files?.[0];if(!n)return;r.value="";let a;try{const t=await n.text();a=JSON.parse(t)}catch{E.toast("Not a valid .dmblock.json file (could not parse JSON).",{type:"error"});return}if(!a||typeof a!="object"||!a.name||typeof a.html!="string"){E.toast("Bundle is missing a name or html field.",{type:"error"});return}try{const t=await i.blocks.importBundle(a);E.toast(`Imported "${t.name}".`,{type:"success"}),await l(e)}catch(t){if(t.code==="CONFLICT"){if(!await E.confirm(`A block named "${t.name}" already exists. Overwrite it with the imported version?`)){E.toast("Import cancelled.",{type:"info"});return}try{const c=await i.blocks.importBundle(a,{overwrite:!0});E.toast(`Overwrote "${c.name}".`,{type:"success"}),await l(e)}catch(c){E.toast(c.message||"Import failed.",{type:"error"})}return}E.toast(t.message||"Import failed.",{type:"error"})}}))}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{api as h}from"../api.js";function M(t){return String(t).replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""")}let y=null,g=null,p=[],j=1,w=[],x={};const z=50;export const collectionEntriesView={templateUrl:"/admin/js/templates/collection-entries.html",async onMount(t){p=[],j=1,g=null,w=[],x={};const d=window.location.hash.match(/\/collections\/([^/?#]+)\/entries/);if(y=d?d[1]:null,!y){E.toast("No collection selected.",{type:"error"});return}const a=E.loader(t.get(0),{type:"dots"});try{if(g=await h.collections.get(y),!g){a.destroy(),E.toast("Collection not found.",{type:"error"}),R.navigate("/collections");return}const s=t.find("#entries-title").get(0);s&&(s.textContent=g.title+" \u2014 Entries");const e=t.find("#edit-schema-btn").get(0);e&&e.setAttribute("href",`#/collections/edit/${y}`);const r=t.find("#form-btn").get(0);r&&(new Set(["roles","user-profiles"]).has(y)?r.style.display="none":r.setAttribute("href",`#/forms/edit/${y}`))}catch{a.destroy(),E.toast("Failed to load collection schema.",{type:"error"});return}try{w=await h.actions.forCollection(y)}catch{w=[]}try{await v(t)}finally{a.destroy()}await k(t);const c=t.find("#entry-search").get(0);c&&c.addEventListener("input",()=>{const s=c.value.toLowerCase().trim(),e=s?p.filter(r=>Object.values(r.data||{}).some(n=>String(n).toLowerCase().includes(s))):p;C(e,t)}),t.find("#add-entry-btn").off("click").on("click",()=>{A(null,t)}),t.find("#export-btn").off("click").on("click",()=>{const s=E.slideover({title:"Export Entries",size:"sm",position:"right"}),e=document.createElement("div");e.style.cssText="padding:1.25rem;display:flex;flex-direction:column;gap:1rem;",["json","csv"].forEach(r=>{const n=document.createElement("button");n.className="btn btn-ghost",n.style.cssText="justify-content:flex-start;gap:.5rem;",n.textContent=r==="json"?"Export as JSON":"Export as CSV",n.addEventListener("click",()=>{fetch(`/api/collections/${y}/export?format=${r}`,{headers:{Authorization:`Bearer ${S.get("auth_token")||""}`}}).then(l=>l.blob()).then(l=>{const o=document.createElement("a");o.href=URL.createObjectURL(l),o.download=`${y}-entries.${r}`,document.body.appendChild(o),o.click(),document.body.removeChild(o),URL.revokeObjectURL(o.href),s.close()}).catch(()=>E.toast("Export failed.",{type:"error"}))}),e.appendChild(n)}),s.setContent(e),s.open()}),t.find("#import-btn").off("click").on("click",()=>{B(t)}),t.find("#clear-all-btn").off("click").on("click",async()=>{if(await E.confirm("Delete ALL entries? This cannot be undone."))try{await h.collections.clearEntries(y),p=[],E.toast("All entries cleared.",{type:"success"}),C([],t)}catch{E.toast("Failed to clear entries.",{type:"error"})}}),Domma.icons.scan()}};async function k(t){const m=w.filter(a=>a.access?.rowLevel);if(m.length===0)return;const d=p.map(a=>a.id);d.length!==0&&(await Promise.all(m.map(async a=>{try{const{allowed:c}=await h.actions.checkAccess(a.slug,d);x[a.slug]=new Set(c)}catch{x[a.slug]=null}})),C(p,t))}async function v(t){try{const m=await h.collections.listEntries(y,{limit:500});p=m.entries||m||[]}catch{p=[],E.toast("Could not load entries.",{type:"error"})}C(p,t),await k(t)}function C(t,m){const d=m.find("#entry-count").get(0);d&&(d.textContent=t.length===p.length?`${p.length} entr${p.length!==1?"ies":"y"}`:`Showing ${t.length} of ${p.length}`);const c=[...(g?.fields||[]).slice(0,5).map(e=>({key:`data.${e.name}`,title:e.label||e.name,render:(r,n)=>{const l=n.data?.[e.name]??"",o=String(l),i=document.createElement("span");return i.title=o,i.textContent=o.length>60?o.slice(0,60)+"\u2026":o,i.outerHTML}})),{key:"meta",title:"Created",render:e=>D(e?.createdAt).format("DD MMM YYYY HH:mm")},{key:"id",title:"",render:e=>{const r=document.createElement("div");r.style.cssText="display:flex;gap:.3rem;justify-content:flex-end;flex-wrap:wrap;",w.forEach(o=>{if(x[o.slug]!==void 0&&x[o.slug]!==null&&!x[o.slug].has(e))return;const i=document.createElement("button");i.className="btn btn-sm btn-ghost js-run-action",i.dataset.actionSlug=o.slug,i.dataset.entryId=e,i.dataset.confirmMessage=o.trigger?.confirmMessage||"",i.title=o.title||o.slug;const f=document.createElement("span");f.setAttribute("data-icon",M(o.trigger?.icon||"zap")),i.appendChild(f),i.appendChild(document.createTextNode(" "+(o.trigger?.label||o.title))),r.appendChild(i)});const n=document.createElement("button");n.className="btn btn-sm btn-ghost js-edit-entry",n.dataset.id=e,n.setAttribute("data-tooltip","Edit"),n.innerHTML='<span data-icon="edit"></span>';const l=document.createElement("button");return l.className="btn btn-sm btn-danger js-delete-entry",l.dataset.id=e,l.setAttribute("data-tooltip","Delete"),l.innerHTML='<span data-icon="trash"></span>',r.appendChild(n),r.appendChild(l),r.outerHTML}}];T.create("#entries-table",{data:t,columns:c,emptyMessage:'No entries yet. Click "Add Entry" to get started.'});const s=document.querySelector("#entries-table");s&&(s.querySelectorAll(".js-run-action").forEach(e=>{e.addEventListener("click",async r=>{r.stopPropagation();const{actionSlug:n,entryId:l,confirmMessage:o}=e.dataset;if(!(o&&!await E.confirm(o))){e.disabled=!0;try{const i=await h.actions.execute(n,l);if(i.success)E.toast(`Action completed (${i.stepsCompleted} step${i.stepsCompleted!==1?"s":""}).`,{type:"success"}),await v({find:f=>({get:u=>document.querySelector(f)})});else{const f=i.results?.find(u=>!u.success);E.toast(f?.error||"Action failed.",{type:"error"})}}catch(i){E.toast(i.message||"Action failed.",{type:"error"})}finally{e.disabled=!1}}})}),s.querySelectorAll(".js-edit-entry").forEach(e=>{e.addEventListener("click",r=>{r.stopPropagation();const n=p.find(l=>l.id===e.dataset.id);n&&A(n,null)})}),s.querySelectorAll(".js-delete-entry").forEach(e=>{e.addEventListener("click",async r=>{if(r.stopPropagation(),!!await E.confirm("Delete this entry?"))try{await h.collections.deleteEntry(y,e.dataset.id),p=p.filter(l=>l.id!==e.dataset.id),E.toast("Entry deleted.",{type:"success"}),C(p,{find:l=>({get:o=>document.querySelector(l)})})}catch{E.toast("Failed to delete entry.",{type:"error"})}})}),s.querySelectorAll("tbody tr").forEach((e,r)=>{const n=t[r];n&&(e.style.cursor="pointer",e.addEventListener("click",l=>{l.target.closest(".js-edit-entry, .js-delete-entry")||q(n)}))}),Domma.icons.scan(s),s.querySelectorAll("[data-tooltip]").forEach(e=>{E.tooltip(e,{content:e.getAttribute("data-tooltip"),position:"top"})}))}function N(t,m){const d={};return t.forEach(a=>{const c={};a.fullWidth?c.span=m:a.span>1&&(c.span=a.span),a.placeholder&&(c.placeholder=a.placeholder),a.helper&&(c.hint=a.helper);const s=a.type==="checkbox"?"boolean":a.type==="date"?"string":a.type;d[a.name]={type:s,label:a.label,required:a.required,options:a.options,...Object.keys(c).length?{formConfig:c}:{}}}),d}function A(t,m){const d=!!t,a=E.modal({title:d?"Edit Entry":"Add Entry",size:"md"}),c=document.createElement("div");c.style.cssText="padding:.25rem 0 .5rem;";const s=g?.fields||[],e=g?.columns||2,r=g?.layout||"stacked",n=document.createElement("div");F.create(N(s,e),t?.data||{},{layout:r,columns:e,showSubmitButton:!1}).renderTo(n),(s||[]).forEach(f=>{if(f.type!=="date"||!f.name)return;const u=n.querySelector(`[name="${f.name}"]`);u&&u.type!=="date"&&(u.type="date")}),c.appendChild(n);const l=document.createElement("div");l.style.cssText="display:flex;justify-content:flex-end;gap:.5rem;margin-top:.5rem;";const o=document.createElement("button");o.className="btn btn-ghost",o.textContent="Cancel";const i=document.createElement("button");i.className="btn btn-primary",i.textContent=d?"Save Changes":"Add Entry",l.appendChild(o),l.appendChild(i),c.appendChild(l),a.element.appendChild(c),a.open(),o.addEventListener("click",()=>a.close()),i.addEventListener("click",async()=>{const f={};s.forEach(u=>{const b=n.querySelector(`[name="${u.name}"]`);b&&(f[u.name]=u.type==="checkbox"?b.checked:b.value)}),i.disabled=!0;try{if(d){const u=await h.collections.updateEntry(y,t.id,f),b=p.findIndex(L=>L.id===t.id);b!==-1&&(p[b]=u),E.toast("Entry updated.",{type:"success"})}else{const u=await h.collections.createEntry(y,f);p.unshift(u),E.toast("Entry added.",{type:"success"})}a.close(),C(p,m||{find:()=>({get:()=>null})})}catch(u){E.toast(u.message||"Failed to save entry.",{type:"error"}),i.disabled=!1}})}function q(t){const m=document.createElement("div"),d=g?.fields||[],a=document.createElement("div");a.style.cssText="display:flex;flex-direction:column;gap:.75rem;margin-bottom:1.25rem;",d.forEach(e=>{const r=t.data?.[e.name];if(r==null||r==="")return;const n=document.createElement("div");n.style.cssText="border-bottom:1px solid var(--dm-border,#333);padding-bottom:.6rem;";const l=document.createElement("strong");l.textContent=e.label||e.name,l.style.cssText="display:block;font-size:.8rem;color:var(--dm-text-muted,#888);margin-bottom:.2rem;";const o=document.createElement("p");o.textContent=String(r),o.style.cssText="margin:0;word-break:break-word;",n.appendChild(l),n.appendChild(o),a.appendChild(n)});const c=document.createElement("div");if(c.style.cssText="font-size:.8rem;color:var(--dm-text-muted,#888);display:flex;flex-direction:column;gap:.3rem;border-top:1px solid var(--dm-border,#333);padding-top:.75rem;",t.meta?.createdAt){const e=document.createElement("span");e.textContent=`Created: ${D(t.meta.createdAt).format("DD MMM YYYY HH:mm")}`,c.appendChild(e)}if(t.meta?.source){const e=document.createElement("span");e.textContent=`Source: ${t.meta.source}`,c.appendChild(e)}m.appendChild(a),m.appendChild(c);const s=E.modal({title:"Entry Details",size:"md"});s.element.appendChild(m),s.open()}function B(t){const m=E.modal({title:"Import Entries",size:"md"}),d=document.createElement("div");d.style.cssText="padding:.25rem 0 .5rem;display:flex;flex-direction:column;gap:.75rem;";const a=document.createElement("p");a.textContent='Paste a JSON array of entries. Each item should have a "data" object with field values.',a.style.cssText="font-size:.875rem;color:var(--dm-text-muted,#888);margin:0;",d.appendChild(a);const c=document.createElement("textarea");c.className="form-input",c.rows=10,c.placeholder='[{"data": {"name": "Example"}}, ...]',d.appendChild(c);const s=document.createElement("p");s.style.cssText="font-size:.875rem;margin:0;display:none;",d.appendChild(s);const e=document.createElement("div");e.style.cssText="display:flex;justify-content:flex-end;gap:.5rem;";const r=document.createElement("button");r.className="btn btn-ghost",r.textContent="Cancel";const n=document.createElement("button");n.className="btn btn-primary",n.textContent="Import",e.appendChild(r),e.appendChild(n),d.appendChild(e),m.element.appendChild(d),m.open(),r.addEventListener("click",()=>m.close()),n.addEventListener("click",async()=>{let l;try{if(l=JSON.parse(c.value),!Array.isArray(l))throw new Error("Must be a JSON array")}catch(o){s.style.display="",s.style.color="var(--dm-danger,#f87171)",s.textContent=`Invalid JSON: ${o.message}`;return}n.disabled=!0;try{const o=await h.collections.import(y,l);s.style.display="",s.style.color="var(--dm-success,#4ade80)",s.textContent=`Imported ${o.imported} entries. Skipped: ${o.skipped}.`,await v(t||{find:()=>({get:()=>null,off:()=>({on:()=>{}})})}),setTimeout(()=>m.close(),1500)}catch(o){s.style.display="",s.style.color="var(--dm-danger,#f87171)",s.textContent=o.message||"Import failed."}finally{n.disabled=!1}})}
|
|
1
|
+
import{api as h}from"../api.js";import{keepTableIconsAlive as M}from"../lib/table-icons.js";function L(t){return String(t).replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""")}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-danger 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 +1 @@
|
|
|
1
|
-
import{api as
|
|
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,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""")}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-danger 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-danger",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,5 +1,5 @@
|
|
|
1
|
-
import{api as c}from"../api.js";import{filterByProject as u,getProjectFromHash as f}from"../lib/project-context.js";function
|
|
2
|
-
<a href="#/components/edit/${a}" class="btn btn-sm btn-ghost" data-tooltip="Edit"><span data-icon="edit"></span></a>
|
|
3
|
-
<button class="btn btn-sm btn-ghost js-export-component" data-name="${a}" data-tooltip="Export as .dmcomponent.json"><span data-icon="download"></span></button>
|
|
1
|
+
import{api as c}from"../api.js";import{filterByProject as u,getProjectFromHash as f}from"../lib/project-context.js";import{keepTableIconsAlive as g}from"../lib/table-icons.js";function p(t){return String(t??"").replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""")}function y(t){for(;t.firstChild;)t.removeChild(t.firstChild)}export const componentsView={templateUrl:"/admin/js/templates/components.html",async onMount(t){h(),await l(t),b(t),Domma.icons.scan(t.get(0)),g(t.get(0))}};let d=!1;function h(){d||(d=!0,I.register("download",{viewBox:"0 0 24 24",paths:["M12 3v12","M7 10l5 5 5-5","M5 19h14"],stroke:"currentColor",fill:"none",strokeWidth:2,strokeLinecap:"round",strokeLinejoin:"round"}),I.register("upload",{viewBox:"0 0 24 24",paths:["M12 21V9","M7 14l5-5 5 5","M5 5h14"],stroke:"currentColor",fill:"none",strokeWidth:2,strokeLinecap:"round",strokeLinejoin:"round"}),I.register("plus",{viewBox:"0 0 24 24",paths:["M12 5v14","M5 12h14"],stroke:"currentColor",fill:"none",strokeWidth:2,strokeLinecap:"round",strokeLinejoin:"round"}))}async function l(t){const o=t.find("#components-table-container").get(0);if(!o)return;let n=[];const i=E.loader(t.get(0),{type:"dots"});try{n=await c.components.list()}catch(e){y(o);const r=document.createElement("p");r.className="text-muted",r.textContent=`Failed to load components: ${e.message}`,o.appendChild(r);return}finally{i.destroy()}const s=f();s&&(n=u(n,s)),T.create(o,{data:n,emptyMessage:'No components yet. Click "New Component" to create your first one.',columns:[{key:"name",title:"Name",render:(e,r)=>{const a=r.origin==="plugin"?' <span class="badge badge-secondary" title="Provided by a plugin \u2014 read-only">plugin</span>':"";return`<a href="#/components/edit/${p(e)}">${p(e)}.dmc</a>${a}`}},{key:"props",title:"Props",render:e=>{const r=Object.keys(e||{});return r.length===0?'<span class="text-muted">\u2014</span>':r.map(a=>`<span class="badge badge-secondary" style="margin-right:.25rem;">${p(a)}</span>`).join("")}},{key:"size",title:"Size",render:e=>`${e} B`},{key:"updatedAt",title:"Updated",render:e=>e?new Date(e).toLocaleString():"\u2014"},{key:"name",title:"Actions",render:(e,r)=>{const a=p(e),m=r.origin==="plugin"?"":`<button class="btn btn-sm btn-danger js-delete-component" data-name="${a}" data-tooltip="Delete"><span data-icon="trash" data-icon-size="14"></span></button>`;return`<div style="display:flex;gap:.4rem;justify-content:flex-end;">
|
|
2
|
+
<a href="#/components/edit/${a}" class="btn btn-sm btn-ghost" data-tooltip="Edit"><span data-icon="edit" data-icon-size="14"></span></a>
|
|
3
|
+
<button class="btn btn-sm btn-ghost js-export-component" data-name="${a}" data-tooltip="Export as .dmcomponent.json"><span data-icon="download" data-icon-size="14"></span></button>
|
|
4
4
|
${m}
|
|
5
|
-
</div>`}}]}),
|
|
5
|
+
</div>`}}]}),t.off("click",".js-delete-component").on("click",".js-delete-component",async function(){await k(this.dataset.name,t)}),t.off("click",".js-export-component").on("click",".js-export-component",async function(){try{await c.components.exportBundle(this.dataset.name)}catch(e){E.toast(e.message||"Export failed.",{type:"error"})}}),Domma.icons.scan(o),o.querySelectorAll("[data-tooltip]").forEach(e=>{E.tooltip(e,{content:e.getAttribute("data-tooltip"),position:"top"})})}async function k(t,o){if(await E.confirm(`Delete component "${t}"? This cannot be undone.`))try{await c.components.delete(t),E.toast("Component deleted.",{type:"success"}),await l(o)}catch(i){E.toast(i.message||"Failed to delete component.",{type:"error"})}}function b(t){const o=t.find("#import-component-btn").get(0),n=t.find("#import-component-file").get(0);!o||!n||(o.addEventListener("click",()=>n.click()),n.addEventListener("change",async()=>{const i=n.files?.[0];if(!i)return;n.value="";let s;try{const e=await i.text();s=JSON.parse(e)}catch{E.toast("Not a valid .dmcomponent.json file (could not parse JSON).",{type:"error"});return}if(!s||typeof s!="object"||!s.name||typeof s.source!="string"){E.toast("Bundle is missing a name or source field.",{type:"error"});return}try{const e=await c.components.importBundle(s);E.toast(`Imported "${e.name}".`,{type:"success"}),await l(t)}catch(e){if(e.code==="CONFLICT"){if(!await E.confirm(`A component named "${e.name}" already exists. Overwrite it with the imported version?`)){E.toast("Import cancelled.",{type:"info"});return}try{const a=await c.components.importBundle(s,{overwrite:!0});E.toast(`Overwrote "${a.name}".`,{type:"success"}),await l(t)}catch(a){E.toast(a.message||"Import failed.",{type:"error"})}return}E.toast(e.message||"Import failed.",{type:"error"})}}))}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{apiRequest as x}from"/admin/js/api.js";let c=null,k=[],r=[];export const formSubmissionsView={templateUrl:"/admin/js/templates/form-submissions.html",async onMount(
|
|
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 s=await x(`/forms/${c}`);k=(s.fields||[]).filter(n=>n.type!=="page-break"),e.find("#submissions-title").get(0).textContent=`${s.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(s){s.stopPropagation();const n=this.dataset.id;if(await E.confirm("Delete this submission?"))try{await x(`/forms/${c}/submissions/${n}`,{method:"DELETE"}),r=r.filter(d=>d.id!==n),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 s=E.slideover({title:"Export Submissions",size:"sm",position:"right"}),n=document.createElement("div");n.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),s.close()}).catch(()=>E.toast("Export failed.",{type:"error"}))}),C.appendChild(b),C}n.appendChild(i("Export as CSV","file-text",`/api/forms/${c}/submissions/export`,`${c}-submissions.csv`)),n.appendChild(i("Export as JSON","code",`/api/forms/${c}/submissions/export/json`,`${c}-submissions.json`)),s.setContent(n),Domma.icons.scan(n),s.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),o=e.find("#sub-date-from").get(0),t=e.find("#sub-date-to").get(0);function a(){const s=m?.value.toLowerCase()||"",n=o?.value?new Date(o.value):null,i=t?.value?new Date(t.value+"T23:59:59"):null,d=r.filter(y=>{if(s&&!Object.values(y.data||{}).map(g=>String(g).toLowerCase()).some(g=>g.includes(s)))return!1;if(n||i){const f=y.meta?.createdAt?new Date(y.meta.createdAt):null;if(!f||n&&f<n||i&&f>i)return!1}return!0});v(d,e)}m&&m.addEventListener("input",a),o&&o.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,s)=>{const n=s.data?.[t.name]??"",i=String(n),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-danger 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 o=document.querySelector("#submissions-table");o&&(o.querySelectorAll("tbody tr").forEach((t,a)=>{const s=e[a];s&&(t.style.cursor="pointer",t.addEventListener("click",n=>{n.target.closest(".js-delete-submission")||j(s)}))}),Domma.icons.scan(o),o.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(o=>{const t=e.data?.[o.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 s=document.createElement("strong");s.textContent=o.label||o.name,s.style.cssText="display:block;font-size:.8rem;color:var(--dm-text-muted,#888);margin-bottom:.2rem;";const n=document.createElement("p");n.textContent=String(t),n.style.cssText="margin:0;word-break:break-word;",a.appendChild(s),a.appendChild(n),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 o=document.createElement("span");o.textContent=`Submitted: ${D(e.meta.createdAt).format("DD MMM YYYY HH:mm")}`,u.appendChild(o)}if(e.meta?.ip){const o=document.createElement("span");o.textContent=`IP: ${e.meta.ip}`,u.appendChild(o)}p.appendChild(l),p.appendChild(u);const m=E.modal({title:"Submission Details",size:"md"});m.element.appendChild(p),m.open()}
|
package/admin/js/views/forms.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{apiRequest as p}from"/admin/js/api.js";import{filterByProject as
|
|
1
|
+
import{apiRequest as p}from"/admin/js/api.js";import{filterByProject as y,getProjectFromHash as h}from"/admin/js/lib/project-context.js";import{keepTableIconsAlive as C}from"/admin/js/lib/table-icons.js";function u(r){return String(r).replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""").replace(/'/g,"'")}export const formsView={templateUrl:"/admin/js/templates/forms.html",async onMount(r){await g(r),C(r.get(0)),r.off("click",".js-delete-form").on("click",".js-delete-form",async function(){const c=this.dataset.slug,i=this.dataset.plugin,l=i?`This form is managed by the <strong>${i}</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(l))try{await p(`/forms/${c}`,{method:"DELETE"}),E.toast("Form deleted.",{type:"success"}),await g(r)}catch{E.toast("Failed to delete form.",{type:"error"})}}),r.find("#create-form-btn").off("click").on("click",()=>{const c=E.modal({title:"Create Form",size:"sm"}),i=document.createElement("div");i.style.cssText="padding:.25rem 0 .5rem;";const l=document.createElement("div");F.create({title:{type:"string",label:"Form Title",placeholder:"e.g. Contact, Feedback\u2026",required:!0}},{},{showSubmitButton:!1}).renderTo(l),i.appendChild(l);const e=document.createElement("div");e.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 s=document.createElement("button");s.className="btn btn-primary",s.textContent="Create",e.appendChild(n),e.appendChild(s),i.appendChild(e),c.element.appendChild(i),c.open();const t=l.querySelector('input[name="title"]');setTimeout(()=>t?.focus(),50);async function a(){const o=t?.value.trim();if(o)try{const d=await p("/forms",{method:"POST",body:JSON.stringify({title:o})});c.close(),R.navigate(`/forms/edit/${d.slug}`)}catch(d){E.toast(d.message||"Failed to create form.",{type:"error"})}}n.addEventListener("click",()=>c.close()),s.addEventListener("click",a),t?.addEventListener("keydown",o=>{o.key==="Enter"&&a()})}),r.find("#test-email-btn").off("click").on("click",async()=>{const i=(window.__CMS_SITE__?.smtp||{}).fromAddress||"",l=E.modal({title:"Send Test Email",size:"sm"}),e=document.createElement("div");e.style.cssText="padding:.25rem 0 .5rem;";const n=document.createElement("div");n.className="mb-3";const s=document.createElement("label");s.className="form-label",s.textContent="Send to";const t=document.createElement("input");t.type="email",t.className="form-input",t.value=i,t.placeholder="test@example.com",n.appendChild(s),n.appendChild(t),e.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.",e.appendChild(a);const o=document.createElement("div");o.style.cssText="display:flex;justify-content:flex-end;gap:.5rem;margin-top:.75rem;";const d=document.createElement("button");d.className="btn btn-ghost",d.textContent="Cancel";const m=document.createElement("button");m.className="btn btn-primary",m.textContent="Send",o.appendChild(d),o.appendChild(m),e.appendChild(o),l.element.appendChild(e),l.open(),setTimeout(()=>t?.focus(),50),d.addEventListener("click",()=>l.close()),m.addEventListener("click",async()=>{const f=t.value.trim();if(f){m.disabled=!0;try{await p("/forms/test-email",{method:"POST",body:JSON.stringify({to:f})}),l.close(),E.toast("Test email sent.",{type:"success"})}catch(b){E.toast(b.message||"Failed to send test email.",{type:"error"})}finally{m.disabled=!1}}})}),Domma.icons.scan()}};async function g(r){const c=E.loader(r.get(0),{type:"dots"});let i=[];try{i=await p("/forms")}catch{E.toast("Could not load forms.",{type:"error"})}finally{c.destroy()}const l=h();l&&(i=y(i,l)),T.create("#forms-table",{data:i,columns:[{key:"title",title:"Title",render:(e,n)=>{const s=document.createElement("span");s.style.cssText="display:inline-flex;align-items:center;gap:.4rem;flex-wrap:wrap;";const t=document.createElement("a");if(t.href=`#/forms/edit/${u(n.slug)}`,t.textContent=e,t.style.fontWeight="600",s.appendChild(t),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;",s.appendChild(a)}return s.outerHTML}},{key:"slug",title:"Slug",render:e=>{const n=document.createElement("code");return n.textContent=e,n.outerHTML}},{key:"fields",title:"Field Count",render:e=>String(e?.length??0)},{key:"submissionCount",title:"Submission Count",render:e=>String(e??0)},{key:"slug",title:"Actions",render:(e,n)=>{const s=document.createElement("div");s.style.cssText="display:flex;gap:.4rem;justify-content:flex-end;";const t=document.createElement("a");t.href=`#/forms/edit/${u(e)}`,t.className="btn btn-sm btn-ghost",t.setAttribute("data-tooltip","Edit"),t.innerHTML='<span data-icon="edit" data-icon-size="14"></span>';const a=document.createElement("a");a.href=`#/forms/${u(e)}/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 o=document.createElement("button");return o.className="btn btn-sm btn-danger js-delete-form",o.dataset.slug=e,o.dataset.plugin=n.plugin||"",o.setAttribute("data-tooltip","Delete"),o.innerHTML='<span data-icon="trash" data-icon-size="14"></span>',s.appendChild(t),s.appendChild(a),s.appendChild(o),s.outerHTML}}],emptyMessage:'No forms yet. Click "Create Form" to get started.'}),Domma.icons.scan(),document.querySelectorAll("#forms-table [data-tooltip]").forEach(e=>{E.tooltip(e,{content:e.getAttribute("data-tooltip"),position:"top"})})}
|
package/admin/js/views/menus.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import{api as o}from"../api.js";import{filterByProject as b,getProjectFromHash as y}from"../lib/project-context.js";function l(
|
|
2
|
-
<a class="btn btn-sm btn-ghost" href="#/menus/edit/${encodeURIComponent(t.slug)}" data-tooltip="Edit"><span data-icon="edit"></span></a>
|
|
3
|
-
<button class="btn btn-sm btn-ghost btn-duplicate" data-slug="${u}" data-tooltip="Duplicate"><span data-icon="copy"></span></button>
|
|
4
|
-
<button class="btn btn-sm btn-danger btn-delete" data-slug="${u}" ${g} ${i}><span data-icon="trash"></span></button>
|
|
5
|
-
`}}]}),Domma.icons.scan("#menus-table"),document.querySelectorAll("#menus-table [data-tooltip]").forEach(e=>{E.tooltip(e,{content:e.getAttribute("data-tooltip"),position:"top"})}),
|
|
1
|
+
import{api as o}from"../api.js";import{filterByProject as b,getProjectFromHash as y}from"../lib/project-context.js";import{keepTableIconsAlive as f}from"../lib/table-icons.js";function l(n){return String(n).replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""").replace(/'/g,"'")}export const menusView={templateUrl:"/admin/js/templates/menus.html",async onMount(n){const p=E.loader(n.get(0),{type:"dots"});let c,r;try{[c,r]=await Promise.all([o.menus.list().catch(()=>[]),o.menuLocations.get().catch(()=>({}))])}finally{p.destroy()}const d=y(),m=d?b(c,d):c,s={};for(const[e,t]of Object.entries(r||{}))t&&(s[t]||(s[t]=[]),s[t].push(e));T.create("#menus-table",{data:m,emptyMessage:'No menus yet. Click "New menu" to create one.',columns:[{key:"slug",title:"Slug",sortable:!0,render:e=>`<code>${l(e)}</code>`},{key:"name",title:"Name",sortable:!0,render:(e,t)=>`<a href="#/menus/edit/${encodeURIComponent(t.slug)}" style="font-weight:600;">${l(e||t.slug)}</a>`},{key:"itemsCount",title:"Items",render:(e,t)=>String(t.itemsCount??(Array.isArray(t.items)?t.items.length:0))},{key:"mapped",title:"Mapped",render:(e,t)=>{const a=s[t.slug]||[];return a.length?a.map(i=>`<span class="badge badge-info">${l(i)}</span>`).join(" "):'<span class="text-muted">\u2014</span>'}},{key:"bundled",title:"Bundled",render:(e,t)=>t.meta?.bundled?'<span class="badge badge-secondary">bundled</span>':""},{key:"_actions",title:"Actions",render:(e,t)=>{const a=(s[t.slug]||[]).length>0,i=a?'data-tooltip="Unmap this menu before deleting"':"",g=a?"disabled":"",u=l(t.slug);return`
|
|
2
|
+
<a class="btn btn-sm btn-ghost" href="#/menus/edit/${encodeURIComponent(t.slug)}" data-tooltip="Edit"><span data-icon="edit" data-icon-size="14"></span></a>
|
|
3
|
+
<button class="btn btn-sm btn-ghost btn-duplicate" data-slug="${u}" data-tooltip="Duplicate"><span data-icon="copy" data-icon-size="14"></span></button>
|
|
4
|
+
<button class="btn btn-sm btn-danger btn-delete" data-slug="${u}" ${g} ${i}><span data-icon="trash" data-icon-size="14"></span></button>
|
|
5
|
+
`}}]}),Domma.icons.scan("#menus-table"),f(n.get(0)),document.querySelectorAll("#menus-table [data-tooltip]").forEach(e=>{E.tooltip(e,{content:e.getAttribute("data-tooltip"),position:"top"})}),n.off("click",".btn-duplicate").on("click",".btn-duplicate",async function(){const e=$(this).data("slug");try{const t=await o.menus.duplicate(e);E.toast(`Duplicated as "${t.slug}".`,{type:"success"}),location.reload()}catch(t){E.toast(`Duplicate failed: ${t.message||t}`,{type:"error"})}}),n.off("click",".btn-delete").on("click",".btn-delete",async function(){if(this.disabled)return;const e=$(this).data("slug");if(await E.confirm(`Delete menu "${e}"? This cannot be undone.`))try{await o.menus.remove(e),E.toast("Menu deleted.",{type:"success"}),location.reload()}catch(a){E.toast(`Delete failed: ${a.message||a}`,{type:"error"})}})}};
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import{api as
|
|
2
|
-
<span class="btn btn-sm btn-ghost disabled" aria-disabled="true" style="opacity:.4;pointer-events:none;"><span data-icon="settings"></span></span>`:`<a class="btn btn-sm btn-ghost" href="#/projects/${a}" data-tooltip="Overview"><span data-icon="eye"></span></a>
|
|
3
|
-
<a class="btn btn-sm btn-ghost" href="#/projects/${a}/settings" data-tooltip="Settings"><span data-icon="settings"></span></a>`,g=e.slug==="core"?"":o(e)?`<button class="btn btn-sm btn-ghost btn-toggle-project" data-slug="${s(e.slug)}" data-enable="1" data-tooltip="Enable"><span class="text-success" data-icon="play"></span></button>`:`<button class="btn btn-sm btn-ghost btn-toggle-project" data-slug="${s(e.slug)}" data-enable="0" data-tooltip="Disable"><span class="text-warning" data-icon="pause"></span></button>`,u=e.protected?"":`<button class="btn btn-sm btn-danger btn-delete-project" data-slug="${s(e.slug)}" data-tooltip="Delete (refused while tagged)"><span data-icon="trash"></span></button>`;return`<span style="display:inline-flex;gap:0.25rem;">${
|
|
1
|
+
import{api as l}from"../api.js";import{keepTableIconsAlive as m}from"../lib/table-icons.js";function s(n){return String(n??"").replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""")}export const projectsView={templateUrl:"/admin/js/templates/projects.html",async onMount(n){const d=E.loader(n.get(0),{type:"dots"});let i,r;try{i=await l.projects.list().catch(()=>[]),r=await Promise.all(i.map(t=>l.projects.artefacts(t.slug).then(e=>Object.values(e).reduce((a,c)=>a+c.length,0)).catch(()=>0)))}finally{d.destroy()}const b=i.map((t,e)=>({...t,artefactCount:r[e]})),o=t=>t.enabled===!1;T.create("#projects-table",{data:b,emptyMessage:'No projects yet. Click "New project" to create one.',columns:[{key:"slug",title:"Slug",sortable:!0,render:(t,e)=>o(e)?`<span class="text-muted">${s(t)}</span>`:s(t)},{key:"name",title:"Name",sortable:!0,render:(t,e)=>{const a=o(e)?`<span class="text-muted">${s(t)}</span>`:s(t),c=e.protected?' <span class="badge badge-secondary" data-tooltip="Built-in project \u2014 cannot be deleted">System</span>':"",p=o(e)?' <span class="badge badge-warning" data-tooltip="Disabled \u2014 public pages and external API are off">Disabled</span>':"";return a+c+p}},{key:"icon",title:"Icon",render:(t,e)=>t?`<span class="${o(e)?"text-muted":""}" data-icon="${s(t)}"></span>`:""},{key:"rootUrl",title:"Root URL",render:t=>t?`<code>${s(t)}</code>`:'<span class="text-muted">\u2014</span>'},{key:"artefactCount",title:"Artefacts",render:t=>`<span class="badge badge-info">${t??0}</span>`},{key:"_actions",title:"Actions",render:(t,e)=>{const a=encodeURIComponent(e.slug),c=o(e)?`<span class="btn btn-sm btn-ghost disabled" aria-disabled="true" style="opacity:.4;pointer-events:none;"><span data-icon="eye" data-icon-size="14"></span></span>
|
|
2
|
+
<span class="btn btn-sm btn-ghost disabled" aria-disabled="true" style="opacity:.4;pointer-events:none;"><span data-icon="settings" data-icon-size="14"></span></span>`:`<a class="btn btn-sm btn-ghost" href="#/projects/${a}" data-tooltip="Overview"><span data-icon="eye" data-icon-size="14"></span></a>
|
|
3
|
+
<a class="btn btn-sm btn-ghost" href="#/projects/${a}/settings" data-tooltip="Settings"><span data-icon="settings" data-icon-size="14"></span></a>`,g=e.slug==="core"?"":o(e)?`<button class="btn btn-sm btn-ghost btn-toggle-project" data-slug="${s(e.slug)}" data-enable="1" data-tooltip="Enable"><span class="text-success" data-icon="play" data-icon-size="14"></span></button>`:`<button class="btn btn-sm btn-ghost btn-toggle-project" data-slug="${s(e.slug)}" data-enable="0" data-tooltip="Disable"><span class="text-warning" data-icon="pause" data-icon-size="14"></span></button>`,u=e.protected?"":`<button class="btn btn-sm btn-danger btn-delete-project" data-slug="${s(e.slug)}" data-tooltip="Delete (refused while tagged)"><span data-icon="trash" data-icon-size="14"></span></button>`;return`<span style="display:inline-flex;gap:0.25rem;">${c}${g}${u}</span>`}}]}),Domma.icons.scan(n.get(0)),m(n.get(0)),document.querySelectorAll("#projects-table [data-tooltip]").forEach(t=>{E.tooltip(t,{content:t.getAttribute("data-tooltip"),position:"top"})}),n.off("click",".btn-toggle-project").on("click",".btn-toggle-project",async function(){const t=$(this).data("slug"),e=String($(this).data("enable"))==="1";if(!(!e&&!await E.confirm(`Disable project "${t}"? Its public pages will 404 and its external API will stop responding. Admin editing is unaffected.`)))try{await l.projects.update(t,{enabled:e}),E.toast(e?"Project enabled.":"Project disabled.",{type:"success"}),location.reload()}catch(a){E.toast(`Failed: ${a.message||a}`,{type:"error"})}}),n.off("click",".btn-delete-project").on("click",".btn-delete-project",async function(){const t=$(this).data("slug");if(await E.confirm(`Delete project "${t}"? This cannot be undone.`))try{await l.projects.remove(t),E.toast("Project deleted.",{type:"success"}),location.reload()}catch(e){E.toast(`Delete refused: ${e.message||e}`,{type:"error"})}})}};
|
package/admin/js/views/roles.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{api as y,getUser as C}from"../api.js";import{getProjectFromHash as N}from"../lib/project-context.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
|
|
2
|
-
<a href="#/roles/edit/${e}" class="btn btn-sm btn-ghost" data-tooltip="Edit"><span data-icon="edit"></span></a>
|
|
3
|
-
${n?"":`<button class="btn btn-sm btn-danger btn-delete-role" data-id="${e}" data-name="${
|
|
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(),document.addEventListener("click",async function e(
|
|
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(".")),l=f.size;return`${l} resource${l!==1?"s":""} ${b?"(full)":"(partial)"}`}function x(t){return String(t).replace(/"/g,""")}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 l=[];const c=N(),v=async()=>{l=(await y.get("/collections/roles/entries?limit=100")).entries||[],c&&(l=l.filter(a=>(a?.data?.meta?.project||"core")===c))},h=()=>{T.create("#roles-table",{data:l,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
|
+
<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
|
+
${n?"":`<button class="btn btn-sm btn-danger 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=(o,i)=>{const s=document.createElement("div"),r=document.createElement("label");return r.textContent=o,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(o=>{const i=Object.assign(document.createElement("option"),{value:o.value,textContent:o.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 o=n.value.trim().toLowerCase().replace(/[^a-z0-9-]/g,"-"),i=m.value.trim(),s=parseInt(p.value,10);if(!o||!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:o,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"})}})})}};
|
package/admin/js/views/users.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{api as d,getUser as A}from"../api.js";import{filterByProject as k,getProjectFromHash as M}from"../lib/project-context.js";export const usersView={templateUrl:"/admin/js/templates/users.html",async onMount(r){const m=A(),f=E.loader(r.get(0),{type:"dots"});let[n,y]=await Promise.all([d.users.list().catch(()=>[]),d.get("/collections/roles/entries?limit=100").catch(()=>({entries:[]}))]);f.destroy();const
|
|
2
|
-
<a href="#/users/edit/${t.id}" class="btn btn-sm btn-ghost" data-tooltip="Edit"><span data-icon="edit"></span></a>
|
|
3
|
-
${a?"":`<button class="btn btn-sm btn-danger btn-delete-user" data-id="${t.id}" data-name="${
|
|
4
|
-
`:""}}],emptyMessage:"No users found."}),Domma.icons.scan(),document.querySelectorAll("#users-table [data-tooltip]").forEach(e=>{E.tooltip(e,{content:e.getAttribute("data-tooltip"),position:"top"})})};
|
|
1
|
+
import{api as d,getUser as A}from"../api.js";import{filterByProject as k,getProjectFromHash as M}from"../lib/project-context.js";import{keepTableIconsAlive as j}from"../lib/table-icons.js";export const usersView={templateUrl:"/admin/js/templates/users.html",async onMount(r){const m=A(),f=E.loader(r.get(0),{type:"dots"});let[n,y]=await Promise.all([d.users.list().catch(()=>[]),d.get("/collections/roles/entries?limit=100").catch(()=>({entries:[]}))]);f.destroy();const p=M();p&&(n=k(n,p));const c=y.entries||[],u=o=>{T.create("#users-table",{data:o,columns:[{key:"name",title:"Name"},{key:"email",title:"Email"},{key:"role",title:"Role",render:(e,t)=>{const a=c.find(s=>s.data?.name===e),i=a?.data?.badgeClass||"badge-secondary",b=a?.data?.label||e,g=`<span class="badge ${i}">${b}</span>`,l=Array.isArray(t.additionalRoles)?t.additionalRoles.filter(s=>s&&s!==e).map(s=>`<span class="badge badge-outline" title="Additional role" style="margin-left:.25rem;">${c.find(h=>h.data?.name===s)?.data?.label||s}</span>`).join(""):"";return g+l}},{key:"isActive",title:"Status",render:e=>e?'<span class="badge badge-success">Active</span>':'<span class="badge badge-secondary">Inactive</span>'},{key:"lastLogin",title:"Last login",render:e=>e?D(e).format("DD MMM YYYY HH:mm"):"Never"},{key:"actions",title:"Actions",render:(e,t)=>{const a=t.id===m?.id,i=Object.fromEntries(c.map(l=>[l.data?.name,l.data?.level??99]));return(i[m?.role]??99)<(i[t.role]??99)?`
|
|
2
|
+
<a href="#/users/edit/${t.id}" class="btn btn-sm btn-ghost" data-tooltip="Edit"><span data-icon="edit" data-icon-size="14"></span></a>
|
|
3
|
+
${a?"":`<button class="btn btn-sm btn-danger btn-delete-user" data-id="${t.id}" data-name="${v(t.name)}" data-tooltip="Delete"><span data-icon="trash" data-icon-size="14"></span></button>`}
|
|
4
|
+
`:""}}],emptyMessage:"No users found."}),Domma.icons.scan(),document.querySelectorAll("#users-table [data-tooltip]").forEach(e=>{E.tooltip(e,{content:e.getAttribute("data-tooltip"),position:"top"})})};u(n),j(r.get(0)),r.off("click",".btn-delete-user").on("click",".btn-delete-user",async function(){const o=$(this).data("id"),e=$(this).data("name");if(await E.confirm(`Delete user <strong>${e}</strong>? This cannot be undone.`))try{await d.delete(`/users/${o}`),E.toast("User deleted.",{type:"success"}),n=n.filter(a=>a.id!==o),u(n)}catch(a){E.toast(`Failed to delete: ${a.message}`,{type:"error"})}})}};function v(r){return String(r).replace(/"/g,""")}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{api as
|
|
1
|
+
import{api as l}from"../api.js";import{filterByProject as m,getProjectFromHash as u}from"../lib/project-context.js";import{keepTableIconsAlive as w}from"../lib/table-icons.js";function n(i){return String(i).replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""")}export const viewsListView={templateUrl:"/admin/js/templates/views-list.html",async onMount(i){await d(i),i.find("#create-view-btn").off("click").on("click",()=>{R.navigate("/views/new")}),Domma.icons.scan(),w(i.get(0))}};async function d(i){const p=E.loader(i.get(0),{type:"dots"});let r=[];try{r=await l.views.list()}catch{E.toast("Could not load views.",{type:"error"})}finally{p.destroy()}const c=u();c&&(r=m(r,c)),T.create("#views-table",{data:r,columns:[{key:"title",title:"Title",render:(e,t)=>{const a=document.createElement("a");return a.href=`#/views/${n(t.slug)}/preview`,a.textContent=e,a.style.fontWeight="600",a.outerHTML}},{key:"slug",title:"Slug",render:e=>`<code>${n(e)}</code>`},{key:"pipeline",title:"Source",render:e=>`<code>${n(e?.source||"\u2014")}</code>`},{key:"display",title:"Mode",render:e=>n(e?.mode||"table")},{key:"access",title:"Roles",render:e=>(e?.roles||[]).map(t=>`<span class="badge badge-secondary">${n(t)}</span>`).join(" ")},{key:"slug",title:"Actions",render:e=>{const t=document.createElement("div");t.style.cssText="display:flex;gap:.4rem;justify-content:flex-end;";const a=document.createElement("a");a.href=`#/views/${n(e)}/preview`,a.className="btn btn-sm btn-ghost",a.setAttribute("data-tooltip","Preview"),a.innerHTML='<span data-icon="eye" data-icon-size="14"></span>';const s=document.createElement("a");s.href=`#/views/edit/${n(e)}`,s.className="btn btn-sm btn-ghost",s.setAttribute("data-tooltip","Edit"),s.innerHTML='<span data-icon="edit" data-icon-size="14"></span>';const o=document.createElement("button");return o.className="btn btn-sm btn-danger js-delete-view",o.dataset.slug=e,o.setAttribute("data-tooltip","Delete"),o.innerHTML='<span data-icon="trash" data-icon-size="14"></span>',t.appendChild(a),t.appendChild(s),t.appendChild(o),t.outerHTML}}],emptyMessage:'No views yet. Click "New View" to create your first view.'}),i.off("click",".js-delete-view").on("click",".js-delete-view",async function(){const e=this.dataset.slug;if(await E.confirm(`Delete view "${e}"? This cannot be undone.`))try{await l.views.delete(e),E.toast("View deleted.",{type:"success"}),await d(i)}catch{E.toast("Failed to delete view.",{type:"error"})}}),Domma.icons.scan(),document.querySelectorAll("#views-table [data-tooltip]").forEach(e=>{E.tooltip(e,{content:e.getAttribute("data-tooltip"),position:"top"})})}
|