domma-cms 0.17.0 → 0.21.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +39 -3
- package/admin/css/admin.css +1 -1
- package/admin/css/dashboard.css +1 -1
- package/admin/index.html +2 -2
- package/admin/js/api.js +1 -1
- package/admin/js/app.js +4 -4
- package/admin/js/config/sidebar-config.js +1 -1
- package/admin/js/lib/card-builder.js +3 -3
- package/admin/js/lib/crud-tutorial.js +1 -0
- package/admin/js/lib/effects-builder.js +1 -1
- package/admin/js/lib/markdown-toolbar.js +6 -6
- package/admin/js/lib/project-context.js +1 -0
- package/admin/js/lib/sidebar-renderer.js +4 -0
- package/admin/js/templates/action-editor.html +7 -0
- package/admin/js/templates/block-editor.html +7 -0
- package/admin/js/templates/collection-editor.html +9 -0
- package/admin/js/templates/dashboard/cache.html +32 -0
- package/admin/js/templates/dashboard.html +4 -0
- package/admin/js/templates/form-editor.html +9 -0
- package/admin/js/templates/menu-editor.html +98 -0
- package/admin/js/templates/menu-locations.html +14 -0
- package/admin/js/templates/menus.html +14 -0
- package/admin/js/templates/page-editor.html +9 -2
- package/admin/js/templates/project-detail.html +50 -0
- package/admin/js/templates/project-editor.html +45 -0
- package/admin/js/templates/project-settings.html +60 -0
- package/admin/js/templates/projects.html +13 -0
- package/admin/js/templates/role-editor.html +11 -0
- package/admin/js/templates/settings.html +26 -0
- package/admin/js/templates/tutorials.html +335 -2
- package/admin/js/templates/view-editor.html +7 -0
- package/admin/js/views/action-editor.js +1 -1
- package/admin/js/views/actions-list.js +1 -1
- package/admin/js/views/block-editor-enhance.js +1 -1
- package/admin/js/views/block-editor.js +8 -8
- package/admin/js/views/blocks.js +2 -2
- package/admin/js/views/collection-editor.js +4 -4
- package/admin/js/views/collections.js +1 -1
- package/admin/js/views/dashboard/widgets/activity-feed.js +1 -1
- package/admin/js/views/dashboard/widgets/cache.js +1 -0
- package/admin/js/views/dashboard/widgets/journeys.js +1 -1
- package/admin/js/views/dashboard/widgets/spike-feed.js +1 -1
- package/admin/js/views/dashboard/widgets/top-pages.js +1 -1
- package/admin/js/views/dashboard.js +1 -1
- package/admin/js/views/form-editor.js +6 -6
- package/admin/js/views/forms.js +1 -1
- package/admin/js/views/index.js +1 -1
- package/admin/js/views/menu-editor.js +19 -0
- package/admin/js/views/menu-locations.js +1 -0
- package/admin/js/views/menus.js +5 -0
- package/admin/js/views/page-editor.js +41 -36
- package/admin/js/views/pages.js +3 -3
- package/admin/js/views/project-detail.js +4 -0
- package/admin/js/views/project-editor.js +1 -0
- package/admin/js/views/project-settings.js +1 -0
- package/admin/js/views/projects.js +7 -0
- package/admin/js/views/role-editor.js +1 -1
- package/admin/js/views/roles.js +3 -3
- package/admin/js/views/settings.js +3 -3
- package/admin/js/views/tutorials.js +1 -1
- package/admin/js/views/user-editor.js +1 -1
- package/admin/js/views/users.js +3 -3
- package/admin/js/views/view-editor.js +1 -1
- package/admin/js/views/views-list.js +1 -1
- package/config/cache.json +4 -0
- package/config/cache.json.example +12 -0
- package/config/menu-locations.json +5 -0
- package/config/menus/admin-sidebar.json +185 -0
- package/config/menus/footer.json +33 -0
- package/config/menus/main.json +35 -0
- package/config/menus/sproj-1779696558011-menu.json +17 -0
- package/config/menus/sproj-1779696960337-menu.json +18 -0
- package/config/menus/sproj-1779696985353-menu.json +18 -0
- package/config/site.json +6 -22
- package/package.json +4 -3
- package/plugins/analytics/daily.json +3 -0
- package/plugins/analytics/journeys.json +8 -0
- package/plugins/analytics/lifetime.json +1 -1
- package/public/css/site.css +1 -1
- package/public/js/collection-browser.js +4 -0
- package/public/js/forms.js +1 -1
- package/public/js/site.js +1 -1
- package/server/config.js +12 -1
- package/server/middleware/auth.js +88 -22
- package/server/routes/api/actions.js +58 -5
- package/server/routes/api/auth.js +2 -2
- package/server/routes/api/blocks.js +18 -3
- package/server/routes/api/cache.js +57 -0
- package/server/routes/api/collections.js +201 -8
- package/server/routes/api/forms.js +266 -21
- package/server/routes/api/menu-locations.js +46 -0
- package/server/routes/api/menus.js +115 -0
- package/server/routes/api/navigation.js +2 -0
- package/server/routes/api/pages.js +1 -1
- package/server/routes/api/projects.js +107 -0
- package/server/routes/api/scaffold.js +86 -0
- package/server/routes/api/settings.js +3 -0
- package/server/routes/api/sidebar.js +23 -0
- package/server/routes/api/users.js +32 -7
- package/server/routes/api/views.js +10 -2
- package/server/routes/public.js +88 -7
- package/server/server.js +54 -3
- package/server/services/actions.js +137 -8
- package/server/services/adapters/FileAdapter.js +23 -8
- package/server/services/adapters/MongoAdapter.js +36 -18
- package/server/services/blocks.js +23 -8
- package/server/services/cache/drivers/MemoryDriver.js +118 -0
- package/server/services/cache/drivers/NoneDriver.js +12 -0
- package/server/services/cache/index.js +229 -0
- package/server/services/cache/lru.js +61 -0
- package/server/services/collections.js +102 -12
- package/server/services/content.js +25 -6
- package/server/services/filterEngine.js +281 -0
- package/server/services/forms.js +3 -0
- package/server/services/hooks.js +48 -0
- package/server/services/markdown.js +711 -124
- package/server/services/menus-migration.js +107 -0
- package/server/services/menus.js +422 -0
- package/server/services/permissionRegistry.js +26 -0
- package/server/services/plugins.js +9 -2
- package/server/services/presetCollections.js +22 -0
- package/server/services/projects.js +429 -0
- package/server/services/recipes/contact-list.json +78 -0
- package/server/services/recipes/onboarding.json +426 -0
- package/server/services/references.js +174 -0
- package/server/services/renderer.js +237 -40
- package/server/services/roles.js +6 -1
- package/server/services/rowAccess.js +86 -13
- package/server/services/scaffolder.js +465 -0
- package/server/services/sidebar-migration.js +117 -0
- package/server/services/sitemap.js +112 -0
- package/server/services/userRoles.js +86 -0
- package/server/services/users.js +23 -2
- package/server/services/views.js +19 -4
- package/server/templates/page.html +135 -130
- /package/config/{navigation.json → navigation.json.bak} +0 -0
package/admin/js/views/pages.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import{api as p}from"../api.js";export const pagesView={templateUrl:"/admin/js/templates/pages.html",async onMount(n){const
|
|
1
|
+
import{api as p}from"../api.js";import{filterByProject as m,getProjectFromHash as f}from"../lib/project-context.js";export const pagesView={templateUrl:"/admin/js/templates/pages.html",async onMount(n){const g=E.loader(n.get(0),{type:"dots"});let o=await p.pages.list().catch(()=>[]);g.destroy();const c=f();c&&(o=m(o,c));const l=s=>{T.create("#pages-table",{data:s,columns:[{key:"title",title:"Title",render:(t,e)=>{const a=e.bundled?'<span class="badge badge-outline" style="font-size:0.65rem;padding:1px 6px;color:var(--dm-info,#2563eb);border-color:var(--dm-info,#2563eb);margin-left:.35rem;">Bundled</span>':"",i=e.plugin?`<span class="badge badge-outline" style="font-size:0.65rem;padding:1px 6px;color:var(--dm-warning,#d97706);border-color:var(--dm-warning,#d97706);margin-left:.35rem;" title="Managed by the ${e.plugin} plugin">${e.plugin}</span>`:"";return`<a href="#/pages/edit${e.urlPath}" style="font-weight:600;">${t}</a>${a}${i}`}},{key:"urlPath",title:"URL",render:t=>`<code>${t}</code>`},{key:"layout",title:"Layout"},{key:"status",title:"Status",render:t=>`<span class="badge badge-${t==="published"?"success":"warning"}">${t}</span>`},{key:"tags",title:"Tags",render:t=>Array.isArray(t)&&t.length?t.map(e=>`<span class="badge badge-info badge-pill badge-sm">${e}</span>`).join(" "):"\u2014"},{key:"versionCount",title:"Revisions",render:t=>Number.isFinite(t)&&t>0?`<span class="badge badge-secondary badge-pill badge-sm">${t}</span>`:'<span style="color:var(--dm-text-muted);">0</span>'},{key:"updatedAt",title:"Updated",render:t=>t?D(t).format("DD MMM YYYY"):"\u2014"},{key:"actions",title:"Actions",render:(t,e)=>`
|
|
2
2
|
<a href="#/pages/edit${e.urlPath}" class="btn btn-sm btn-primary">Edit</a>
|
|
3
3
|
<a href="${e.urlPath}" target="_blank" class="btn btn-sm btn-ghost" data-tooltip="View"><span data-icon="external-link"></span></a>
|
|
4
4
|
<button class="btn btn-sm btn-ghost btn-prune" data-path="${e.urlPath}" data-count="${e.versionCount||0}" data-tooltip="Prune revisions"${(e.versionCount||0)===0?" disabled":""}><span data-icon="archive"></span></button>
|
|
5
5
|
<button class="btn btn-sm btn-danger btn-delete" data-path="${e.urlPath}" data-plugin="${e.plugin||""}">Delete</button>
|
|
6
|
-
`}],emptyMessage:'No pages found. <a href="#/pages/new">Create one</a>.'}),Domma.icons.scan(),document.querySelectorAll("#pages-table [data-tooltip]").forEach(t=>{E.tooltip(t,{content:t.getAttribute("data-tooltip"),position:"top"})}),Domma.effects.reveal(".card",{animation:"fade",duration:350})};
|
|
6
|
+
`}],emptyMessage:'No pages found. <a href="#/pages/new">Create one</a>.'}),Domma.icons.scan(),document.querySelectorAll("#pages-table [data-tooltip]").forEach(t=>{E.tooltip(t,{content:t.getAttribute("data-tooltip"),position:"top"})}),Domma.effects.reveal(".card",{animation:"fade",duration:350})};l(o);const b=s=>{const t=n.find("#pages-tree").empty().get(0);if(!s.length){t.textContent="No pages found.";return}const e=s.map(a=>{const i=a.urlPath.split("/").filter(Boolean),r=i.length>1?"/"+i.slice(0,-1).join("/"):null,d=r&&s.some(h=>h.urlPath===r);return{id:a.urlPath,parent_id:d?r:null,name:a.title||a.urlPath,icon:a.status==="published"?"check-circle":"file-text"}});E.treeView(t,{data:e,idKey:"id",parentKey:"parent_id",labelKey:"name",iconKey:"icon",expandedByDefault:!0,onSelect:a=>R.navigate(`/pages/edit${a}`)}),Domma.icons.scan(t)};n.find("#view-table-btn").on("click",function(){n.find("#pages-table").show(),n.find("#pages-tree").hide(),$(this).addClass("btn-primary").removeClass("btn-ghost"),n.find("#view-tree-btn").addClass("btn-ghost").removeClass("btn-primary")}),n.find("#view-tree-btn").on("click",function(){n.find("#pages-table").hide(),n.find("#pages-tree").show(),$(this).addClass("btn-primary").removeClass("btn-ghost"),n.find("#view-table-btn").addClass("btn-ghost").removeClass("btn-primary"),b(o)}),n.find("#view-table-btn, #view-tree-btn").each(function(){E.tooltip(this,{content:this.getAttribute("data-tooltip"),position:"top"})});const u=()=>{const s=n.find("#status-filter").val(),t=n.find("#pages-search").val().toLowerCase().trim(),e=o.filter(a=>!(s&&a.status!==s||t&&!`${a.title} ${a.urlPath} ${(a.tags||[]).join(" ")}`.toLowerCase().includes(t)));l(e)};n.find("#status-filter").off("change").on("change",u),n.find("#pages-search").get(0).addEventListener("input",u),n.off("click",".btn-delete").on("click",".btn-delete",async function(){const s=$(this).data("path"),t=$(this).data("plugin"),e=t?`This page is managed by the <strong>${t}</strong> plugin. Deleting it may cause the plugin to malfunction. Continue?`:`Delete page at <strong>${s}</strong>? This cannot be undone.`;if(await E.confirm(e))try{await p.pages.delete(s),E.toast("Page deleted.",{type:"success"}),o=o.filter(a=>a.urlPath!==s),l(o)}catch{E.toast("Failed to delete page.",{type:"error"})}}),n.off("click",".btn-prune").on("click",".btn-prune",async function(){const s=$(this).data("path"),t=Number($(this).data("count"))||0,e=window.prompt(`This page has ${t} revision${t===1?"":"s"}.
|
|
7
7
|
|
|
8
|
-
How many of the most recent revisions do you want to keep?`,"5");if(e===null)return;const a=Number.parseInt(e,10);if(!Number.isFinite(a)||a<0){E.toast("Please enter a non-negative number.",{type:"error"});return}try{const i=await p.versions.prune(s,a);E.toast(`Pruned ${i.deleted} revision${i.deleted===1?"":"s"}; kept ${i.kept}.`,{type:"success"});const
|
|
8
|
+
How many of the most recent revisions do you want to keep?`,"5");if(e===null)return;const a=Number.parseInt(e,10);if(!Number.isFinite(a)||a<0){E.toast("Please enter a non-negative number.",{type:"error"});return}try{const i=await p.versions.prune(s,a);E.toast(`Pruned ${i.deleted} revision${i.deleted===1?"":"s"}; kept ${i.kept}.`,{type:"success"});const r=o.find(d=>d.urlPath===s);r&&(r.versionCount=i.kept),l(o)}catch(i){E.toast(i.message||"Failed to prune revisions.",{type:"error"})}})}};
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import{api as d}from"../api.js";function o(t){return String(t??"").replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""")}const j=[{key:"pages",text:"Page",icon:"file-text",path:"/pages/new"},{key:"collections",text:"Collection",icon:"database",path:"/collections/new"},{key:"forms",text:"Form",icon:"layout",path:"/forms/new"},{key:"actions",text:"Action",icon:"zap",path:"/actions/new"},{key:"menus",text:"Menu",icon:"menu",path:"/menus/new"},{key:"blocks",text:"Block",icon:"box",path:"/blocks/new"},{key:"views",text:"View",icon:"eye",path:"/views/new"},{key:"roles",text:"Role",icon:"shield",path:"/roles"},{key:"users",text:"User",icon:"users",path:"/users/new"}],h={pages:"Pages",collections:"Collections",forms:"Forms",actions:"Actions",menus:"Menus",blocks:"Blocks",views:"Views",roles:"Roles",users:"Users"};export const projectDetailView={templateUrl:"/admin/js/templates/project-detail.html",async onMount(t){const i=location.hash.split("?")[0].match(/^#\/projects\/([^/]+)$/),s=i?decodeURIComponent(i[1]):null;if(!s){location.hash="#/projects";return}const a=await d.projects.get(s).catch(()=>null);if(!a){E.toast("Project not found.",{type:"error"}),location.hash="#/projects";return}const r=encodeURIComponent(s);t.find("#pd-name").text(a.name||s),t.find("#pd-icon").attr("data-icon",a.icon||"folder"),t.find("#pd-desc").text(a.description||""),t.find("#pd-settings").attr("href","#/projects/"+r+"/settings"),Domma.icons.scan(t.find(".page-header").get(0));const l=await d.projects.artefacts(s).catch(()=>({})),m=t.find("#pd-counts"),u=Object.keys(h).map(e=>{const n=Array.isArray(l[e])?l[e].length:0,c=n>0?`#/projects/${r}/${o(e)}`:null,g=c?`<a href="${c}" class="dm-card-link">`:"",f=c?"</a>":"";return`<div class="card">${g}<div class="card-body dm-count-card">
|
|
2
|
+
<strong>${o(h[e])}</strong>
|
|
3
|
+
<span class="badge badge-${n>0?"info":"secondary"}">${n}</span>
|
|
4
|
+
</div>${f}</div>`});m.html(`<div class="grid grid-cols-3" style="gap:1rem;">${u.join("")}</div>`);const p=t.find("#pd-quick-add");p.html(`<div class="dm-qa-grid">${j.map(e=>`<button class="btn btn-primary pd-qa-btn" data-path="${o(e.path)}" data-project="${o(s)}"><span data-icon="${o(e.icon)}"></span> ${o(e.text)}</button>`).join("")}</div>`),Domma.icons.scan(t.get(0)),p.off("click",".pd-qa-btn").on("click",".pd-qa-btn",function(){const e=$(this).data("path"),n=$(this).data("project");try{sessionStorage.setItem("__projectContext",n)}catch{}location.hash="#"+e})}};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{api as l}from"../api.js";import{makeIconInput as h}from"../lib/shortcode-modal.js";export const projectEditorView={templateUrl:"/admin/js/templates/project-editor.html",async onMount(t){const n=location.hash.match(/^#\/projects\/edit\/(.+)$/),i=n?decodeURIComponent(n[1]):null,r=!i;let e={slug:"",name:"",description:"",icon:"folder",rootUrl:"",sortOrder:0};if(!r)try{e=await l.projects.get(i)}catch(o){E.toast(`Failed to load project: ${o.message||o}`,{type:"error"}),location.hash="#/projects";return}t.find("#pe-title").text(r?"New project":`Edit "${e.slug}"`);const a=t.find("#pe-slug"),p=t.find("#pe-name"),d=t.find("#pe-description"),m=t.find("#pe-rootUrl"),u=t.find("#pe-sortOrder");a.val(e.slug||""),p.val(e.name||""),d.val(e.description||""),m.val(e.rootUrl||""),u.val(e.sortOrder??0),r||a.prop("disabled",!0);const g=t.find("#pe-icon-mount").get(0),s=h("e.g. folder, users, box",e.icon||"folder");s.input.id="pe-icon",s.input.classList.add("form-input"),g.appendChild(s.el),Domma.icons.scan(t.get(0)),t.find("#pe-save").on("click",async()=>{const o={slug:a.val().trim(),name:p.val().trim(),description:d.val().trim(),icon:s.input.value.trim()||"folder",sortOrder:Number.parseInt(u.val(),10)||0},f=m.val().trim();f&&(o.rootUrl=f);try{if(r)await l.projects.create(o);else{const{slug:c,...v}=o;await l.projects.update(i,v)}E.toast("Saved.",{type:"success"}),location.hash="#/projects"}catch(c){E.toast(`Save failed: ${c.message||c}`,{type:"error"})}})}};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{api as a}from"../api.js";import{makeIconInput as h}from"../lib/shortcode-modal.js";export const projectSettingsView={templateUrl:"/admin/js/templates/project-settings.html",async onMount(e){const c=location.hash.split("?")[0].match(/^#\/projects\/([^/]+)\/settings$/),s=c?decodeURIComponent(c[1]):null;if(!s){location.hash="#/projects";return}let o;try{o=await a.projects.get(s)}catch(t){E.toast(`Failed to load project: ${t.message||t}`,{type:"error"}),location.hash="#/projects";return}const f=encodeURIComponent(s);e.find("#ps-title").text(`Settings \u2014 ${o.name||s}`),e.find("#ps-back").attr("href","#/projects/"+f);const u=e.find("#ps-slug"),n=e.find("#ps-name"),i=e.find("#ps-description"),l=e.find("#ps-rootUrl"),p=e.find("#ps-sortOrder");u.val(o.slug||""),n.val(o.name||""),i.val(o.description||""),l.val(o.rootUrl||""),p.val(o.sortOrder??0);const g=e.find("#ps-icon-mount").get(0),r=h("e.g. folder, users, box",o.icon||"folder");r.input.id="ps-icon",r.input.classList.add("form-input"),g.appendChild(r.el),Domma.icons.scan(e.get(0)),e.find("#ps-save").on("click",async()=>{const t={name:n.val().trim(),description:i.val().trim(),icon:r.input.value.trim()||"folder",sortOrder:Number.parseInt(p.val(),10)||0},d=l.val().trim();d&&(t.rootUrl=d);try{await a.projects.update(s,t),E.toast("Saved.",{type:"success"})}catch(m){E.toast(`Save failed: ${m.message||m}`,{type:"error"})}}),e.find("#ps-untag-all").on("click",async()=>{if(await E.confirm(`Untag all artefacts from project "${s}"? They will become site-wide.`))try{const t=await a.projects.untagAll(s);E.toast(`Untagged: ${JSON.stringify(t.untagged)}`,{type:"success"}),setTimeout(()=>location.reload(),600)}catch(t){E.toast(`Untag failed: ${t.message||t}`,{type:"error"})}}),e.find("#ps-delete").on("click",async()=>{if(await E.confirm(`Delete project "${s}"? The server refuses if any artefacts are still tagged.`))try{await a.projects.remove(s),E.toast("Project deleted.",{type:"success"}),location.hash="#/projects"}catch(t){E.toast(`Delete refused: ${t.message||t}`,{type:"error"})}})}};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import{api as o}from"../api.js";function s(a){return String(a??"").replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""")}export const projectsView={templateUrl:"/admin/js/templates/projects.html",async onMount(a){const n=await o.projects.list().catch(()=>[]),c=await Promise.all(n.map(t=>o.projects.artefacts(t.slug).then(e=>Object.values(e).reduce((l,p)=>l+p.length,0)).catch(()=>0))),r=n.map((t,e)=>({...t,artefactCount:c[e]}));T.create("#projects-table",{data:r,emptyMessage:'No projects yet. Click "New project" to create one.',columns:[{key:"slug",title:"Slug",sortable:!0},{key:"name",title:"Name",sortable:!0},{key:"icon",title:"Icon",render:t=>t?`<span 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)=>`
|
|
2
|
+
<span style="display:inline-flex;gap:0.25rem;">
|
|
3
|
+
<a class="btn btn-sm btn-ghost" href="#/projects/${encodeURIComponent(e.slug)}" data-tooltip="Overview"><span data-icon="eye"></span></a>
|
|
4
|
+
<a class="btn btn-sm btn-ghost" href="#/projects/${encodeURIComponent(e.slug)}/settings" data-tooltip="Settings"><span data-icon="settings"></span></a>
|
|
5
|
+
<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>
|
|
6
|
+
</span>
|
|
7
|
+
`}]}),Domma.icons.scan(a.get(0)),document.querySelectorAll("#projects-table [data-tooltip]").forEach(t=>{E.tooltip(t,{content:t.getAttribute("data-tooltip"),position:"top"})}),a.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 o.projects.remove(t),E.toast("Project deleted.",{type:"success"}),location.reload()}catch(e){E.toast(`Delete refused: ${e.message||e}`,{type:"error"})}})}};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{api as
|
|
1
|
+
import{api as j,getUser as X}from"../api.js";const Y=[{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"}];export const roleEditorView={templateUrl:"/admin/js/templates/role-editor.html",async onMount(o){if(!X()){R.navigate("/");return}const N=window.location.hash.match(/\/roles\/edit\/([^/?#]+)/)?.[1];if(!N){R.navigate("/roles");return}E.tabs(o.find("#role-tabs").get(0));const O=E.loader(o.get(0),{type:"dots"});let U,y,M;try{[U,y,M]=await Promise.all([j.get(`/collections/roles/entries/${N}`),j.get("/auth/permissions-registry"),j.projects.list().catch(()=>[])])}catch(e){O.destroy(),E.toast(`Failed to load role: ${e.message}`,{type:"error"}),R.navigate("/roles");return}O.destroy();const a=U.data||{},L=a.level===0,S=Object.fromEntries(y.resources.map(e=>[e.key,e])),C={};y.groups.forEach(e=>{C[e]=[]}),y.resources.forEach(e=>{C[e.group]&&C[e.group].push(e.key)}),o.find("#role-title").text(`Edit Role: ${a.label||a.name}`);const I=o.find("#role-name"),B=o.find("#role-label"),A=o.find("#role-level"),F=o.find("#role-badge");Y.forEach(e=>{const t=document.createElement("option");t.value=e.value,t.textContent=e.label,a.badgeClass===e.value&&(t.selected=!0),F.get(0).appendChild(t)});const V=o.find("#role-project");(M||[]).forEach(e=>{const t=document.createElement("option");t.value=e.slug,t.textContent=e.name||e.slug,a.meta?.project===e.slug&&(t.selected=!0),V.get(0).appendChild(t)}),I.val(a.name||""),B.val(a.label||""),A.val(a.level??""),L&&(I.prop("disabled",!0),A.prop("disabled",!0));const K=o.find("#permissions-container").get(0),q=a.permissions||[],x={},H={},W={},_=(e,t)=>q.includes(e)||q.includes(`${e}.${t}`);y.groups.forEach(e=>{const t=C[e];if(!t.length)return;const l=document.createElement("div");l.className="card mb-3";const r=document.createElement("div");r.className="card-header",r.style.cssText="display:flex;align-items:center;justify-content:space-between;cursor:default;";const g=document.createElement("strong");g.textContent=e;const d=document.createElement("label");d.style.cssText="display:flex;align-items:center;gap:.4rem;font-size:.85rem;font-weight:normal;cursor:pointer;";const p=document.createElement("input");p.type="checkbox",p.style.cursor="pointer",d.appendChild(p),d.appendChild(document.createTextNode("Select All")),W[e]=p,r.appendChild(g),r.appendChild(d),l.appendChild(r);const m=document.createElement("div");m.className="card-body",m.style.cssText="padding:.75rem 1rem;",t.forEach((n,v)=>{const i=S[n],s=i.actions.map(c=>c.key),b=v===t.length-1,T=document.createElement("div");T.style.cssText=`margin-bottom:${b?"0":".75rem"};padding-bottom:${b?"0":".75rem"};${b?"":"border-bottom:1px solid var(--dm-border);"}`;const k=document.createElement("div");k.style.cssText="display:flex;align-items:center;gap:.5rem;margin-bottom:.4rem;";const J=document.createElement("span");J.dataset.icon=i.icon;const G=document.createElement("strong");G.textContent=i.label,G.style.fontSize=".9rem";const w=document.createElement("label");w.style.cssText="margin-left:auto;font-size:.8rem;display:flex;align-items:center;gap:.35rem;cursor:pointer;";const u=document.createElement("input");u.type="checkbox",u.style.cursor="pointer";const Q=s.every(c=>_(n,c));u.checked=Q,H[n]=u,w.appendChild(u),w.appendChild(document.createTextNode("Full Access")),k.appendChild(J),k.appendChild(G),k.appendChild(w),T.appendChild(k);const P=document.createElement("div");P.style.cssText="display:flex;gap:1.5rem;padding-left:1.5rem;flex-wrap:wrap;";const $={};i.actions.forEach(c=>{const f=document.createElement("label");f.style.cssText="display:flex;align-items:center;gap:.3rem;font-size:.875rem;cursor:pointer;";const h=document.createElement("input");h.type="checkbox",h.style.cursor="pointer",h.checked=_(n,c.key),h.dataset.res=n,h.dataset.action=c.key,x[`${n}.${c.key}`]=h,$[c.key]=h,f.appendChild(h),f.appendChild(document.createTextNode(c.label)),P.appendChild(f)}),u.addEventListener("change",()=>{s.forEach(c=>{$[c].checked=u.checked}),z(e,t)}),s.forEach(c=>{$[c].addEventListener("change",()=>{u.checked=s.every(f=>$[f].checked),z(e,t)})}),T.appendChild(P),m.appendChild(T)}),p.addEventListener("change",()=>{t.forEach(n=>{S[n].actions.map(s=>s.key).forEach(s=>{const b=x[`${n}.${s}`];b&&(b.checked=p.checked)});const i=H[n];i&&(i.checked=p.checked)})}),z(e,t),l.appendChild(m),K.appendChild(l)}),Domma.icons.scan("#permissions-container"),o.find("#btn-save-role").on("click",async()=>{const e=I.val().trim().toLowerCase().replace(/[^a-z0-9-]/g,"-"),t=B.val().trim(),l=parseInt(A.val(),10),r=F.val();if(!t){E.toast("Display label is required.",{type:"error"});return}const g=[];y.resources.forEach(m=>{const n=m.key,v=m.actions.map(s=>s.key),i=v.filter(s=>x[`${n}.${s}`]?.checked);i.length===v.length?g.push(n):i.forEach(s=>g.push(`${n}.${s}`))});const d=V.val()||"",p={data:{name:L?a.name:e||a.name,label:t,level:L||isNaN(l)?a.level:l,permissions:g,badgeClass:r,meta:{...a.meta||{},project:d||null}}};try{await j.put(`/collections/roles/entries/${N}`,p),E.toast("Role saved.",{type:"success"}),R.navigate("/roles")}catch(m){E.toast(`Save failed: ${m.message}`,{type:"error"})}});function z(e,t){const l=W[e];l&&(l.checked=t.every(r=>S[r].actions.map(d=>d.key).every(d=>x[`${r}.${d}`]?.checked)))}}};
|
package/admin/js/views/roles.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{api as y,getUser as
|
|
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 k(a){if(!a||!a.length)return"<em>None</em>";const f=new Set(a.map(c=>c.split(".")[0])),b=a.every(c=>!c.includes(".")),l=f.size;return`${l} resource${l!==1?"s":""} ${b?"(full)":"(partial)"}`}function w(a){return String(a).replace(/"/g,""")}export const rolesView={templateUrl:"/admin/js/templates/roles.html",async onMount(a){if(!C()){R.navigate("/");return}const b=E.loader(a.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(t=>t?.data?.meta?.project===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=>k(e.permissions)},{key:"id",title:"Actions",render:(e,t)=>{const n=t.data?.level===0;return`
|
|
2
2
|
<a href="#/roles/edit/${e}" class="btn btn-sm btn-primary">Edit</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()};try{await
|
|
3
|
+
${n?"":`<button class="btn btn-sm btn-danger btn-delete-role" data-id="${e}" data-name="${w(t.data?.label)}">Delete</button>`}
|
|
4
|
+
`}}],emptyMessage:"No roles found."}),Domma.icons.scan()};try{await v()}finally{b.destroy()}h(),document.addEventListener("click",async function e(t){if(!document.contains(a.get(0))){document.removeEventListener("click",e);return}const n=t.target.closest(".btn-delete-role");if(!n)return;const m=n.dataset.id,u=n.dataset.name;if(await E.confirm(`Delete role <strong>${u}</strong>? This cannot be undone.`))try{await y.delete(`/collections/roles/entries/${m}`),E.toast("Role deleted.",{type:"success"}),await v(),h()}catch(p){E.toast(`Failed: ${p.message}`,{type:"error"})}}),a.find("#btn-add-role").on("click",()=>{const e=document.createElement("div");e.style.cssText="display:flex;flex-direction:column;gap:1rem;padding:1rem;";const t=(r,i)=>{const s=document.createElement("div"),o=document.createElement("label");return o.textContent=r,o.style.cssText="display:block;font-size:.85rem;margin-bottom:.25rem;font-weight:600;",s.appendChild(o),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"}),u=Object.assign(document.createElement("input"),{type:"number",className:"form-input",value:"5",min:"1"}),d=document.createElement("select");d.className="form-input",$.forEach(r=>{const i=Object.assign(document.createElement("option"),{value:r.value,textContent:r.label});d.appendChild(i)});const p=Object.assign(document.createElement("button"),{className:"btn btn-primary",textContent:"Create Role"});e.appendChild(t("Name (slug)",n)),e.appendChild(t("Display Label",m)),e.appendChild(t("Level",u)),e.appendChild(t("Badge Colour",d)),e.appendChild(p);const g=E.modal({title:"Add Role",size:"sm"});g.element.appendChild(e),g.open(),p.addEventListener("click",async()=>{const r=n.value.trim().toLowerCase().replace(/[^a-z0-9-]/g,"-"),i=m.value.trim(),s=parseInt(u.value,10);if(!r||!i||isNaN(s)||s<1){E.toast("Name, label and level (\u2265 1) are required.",{type:"error"});return}try{const o=await y.post("/collections/roles/entries",{data:{name:r,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/${o.id}`)}catch(o){E.toast(`Error: ${o.message}`,{type:"error"})}})})}};
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import{api as
|
|
2
|
-
`,
|
|
3
|
-
`,
|
|
1
|
+
import{api as C,apiRequest as v}from"../api.js";import{populateThemeSelect as w}from"../lib/themes.js";export const settingsView={templateUrl:"/admin/js/templates/settings.html",async onMount(e){E.tabs(e.find("#settings-tabs").get(0));const k=E.loader(e.get(0),{type:"dots"}),l=await C.settings.get().catch(()=>({}));if(k.destroy(),e.find("#field-site-title").val(l.title||""),e.find("#field-tagline").val(l.tagline||""),e.find("#field-font-family").val(l.fontFamily||"Roboto"),e.find("#field-font-size").val(l.fontSize||16),w(e.find("#field-theme").get(0)),w(e.find("#field-admin-theme").get(0)),e.find("#field-theme").val(l.baseTheme||l.theme||"charcoal-dark"),l.baseTheme){e.find("#field-theme").prop("disabled",!0);const t=document.createElement("p");t.className="form-hint",t.style.cssText="margin-top:.4rem;font-size:.8rem;color:var(--dm-info);";const s=document.createElement("a");s.href="#/plugins/theme-roller",s.textContent="Theme Roller",t.appendChild(document.createTextNode(`Custom theme \u201C${l.theme}\u201D is active (based on ${l.baseTheme}). Manage via `)),t.appendChild(s),t.appendChild(document.createTextNode(".")),e.find("#field-theme").get(0).closest(".col-6").appendChild(t)}e.find("#field-admin-theme").val(l.adminTheme||"charcoal-dark");const p=l.autoTheme||{},h=!!p.enabled,b=e.find("#field-auto-theme-enabled"),y=e.find("#auto-theme-fields"),u=e.find("#field-theme"),f=u.get(0).innerHTML;e.find("#field-day-theme").html(f),e.find("#field-night-theme").html(f),l.baseTheme?(b.prop("disabled",!0),e.find("#auto-theme-roller-hint").show()):(b.prop("checked",h),h&&(y.show(),u.prop("disabled",!0)),b.on("change",function(){const t=this.checked;y.toggle(t),u.prop("disabled",t)})),e.find("#field-day-theme").val(p.dayTheme||"charcoal-light"),e.find("#field-night-theme").val(p.nightTheme||"charcoal-dark"),e.find("#field-day-start").val(p.dayStart||"07:00"),e.find("#field-night-start").val(p.nightStart||"19:00"),e.find("#field-spacer-size").val(l.layoutOptions?.spacerSize??40),e.find("#field-seo-title").val(l.seo?.defaultTitle||""),e.find("#field-seo-separator").val(l.seo?.titleSeparator||" | "),e.find("#field-seo-desc").val(l.seo?.defaultDescription||""),e.find("#field-footer-copy").val(l.footer?.copyright||""),e.find("#field-social-twitter").val(l.social?.twitter||""),e.find("#field-social-facebook").val(l.social?.facebook||""),e.find("#field-social-instagram").val(l.social?.instagram||""),e.find("#field-social-linkedin").val(l.social?.linkedin||""),e.find("#field-social-github").val(l.social?.github||""),e.find("#field-social-youtube").val(l.social?.youtube||""),e.find("#field-smtp-host").val(l.smtp?.host||""),e.find("#field-smtp-port").val(l.smtp?.port||587),e.find("#field-smtp-user").val(l.smtp?.user||""),e.find("#field-smtp-pass").val(l.smtp?.pass||""),e.find("#field-smtp-secure").prop("checked",l.smtp?.secure||!1),e.find("#field-smtp-from-address").val(l.smtp?.fromAddress||""),e.find("#field-smtp-from-name").val(l.smtp?.fromName||"");const i=l.backToTop||{};e.find("#field-btt-enabled").prop("checked",i.enabled!==!1),e.find("#field-btt-threshold").val(i.scrollThreshold??300),e.find("#field-btt-position").val(i.position||"bottom-right"),e.find("#field-btt-offset").val(i.offset??32),e.find("#field-btt-bottom-offset").val(i.bottomOffset??i.offset??32),e.find("#field-btt-label").val(i.label||""),e.find("#field-btt-smooth").prop("checked",i.smooth!==!1);const d=l.cookieConsent||{};e.find("#field-cc-enabled").prop("checked",d.enabled!==!1),e.find("#field-cc-message").val(d.message||""),e.find("#field-cc-accept-all").val(d.acceptAllText||"Accept All"),e.find("#field-cc-reject-all").val(d.rejectAllText||"Reject All"),e.find("#field-cc-customize").val(d.customizeText||"Customize"),e.find("#field-cc-save-prefs").val(d.savePreferencesText||"Save Preferences"),e.find("#field-cc-privacy-text").val(d.privacyPolicyText||"Privacy Policy"),e.find("#field-cc-privacy-url").val(d.privacyPolicyUrl||""),e.find("#field-cc-cookie-text").val(d.cookiePolicyText||"Cookie Policy"),e.find("#field-cc-cookie-url").val(d.cookiePolicyUrl||""),e.find("#field-cc-position").val(d.position||"bottom"),e.find("#field-cc-layout").val(d.layout||"bar"),e.find("#field-cc-theme").val(d.theme||"dark"),e.find("#field-cc-show-functional").prop("checked",d.showFunctional!==!1),e.find("#field-cc-show-analytics").prop("checked",d.showAnalytics!==!1),e.find("#field-cc-show-marketing").prop("checked",d.showMarketing!==!1),e.find("#field-cc-version").val(d.consentVersion||"1.0");const m=l.breadcrumbs||{};e.find("#field-breadcrumbs-enabled").prop("checked",m.enabled===!0),e.find("#field-breadcrumbs-home-label").val(m.homeLabel||"Home"),e.find("#field-breadcrumbs-position").val(m.position||"TL"),e.find("#field-bc-offset-x").val(m.offsetX??8),e.find("#field-bc-offset-y").val(m.offsetY??8);function c(t){e.find(".bc-pos-btn").each(function(){const s=this.dataset.pos===t;this.style.background=s?"var(--primary, #5b8cff)":"",this.style.color=s?"#fff":""})}c(m.position||"TL"),e.find(".bc-pos-btn").on("click",function(){const t=this.dataset.pos;e.find("#field-breadcrumbs-position").val(t),c(t)});let a=null;try{const{css:t}=await v("/settings/custom-css");e.find("#field-custom-css").val(t||""),E.editor&&(a=E.editor(e.find("#field-custom-css").get(0),{mode:"code",language:"css",lineNumbers:!0,showToolbar:!1,minHeight:420,placeholder:"/* Add your custom CSS here */",characterCount:!0}))}catch{}a&&a._editorEl&&a._editorEl.addEventListener("keydown",t=>{if(!t.ctrlKey&&!t.metaKey)return;if(t.key==="s"){t.preventDefault(),e.find("#save-css-btn").get(0)?.click();return}const s=a._editorEl,n=s.selectionStart!==s.selectionEnd;if((t.key==="c"||t.key==="x")&&!n){t.preventDefault();const o=s.value,g=s.selectionStart,T=o.lastIndexOf(`
|
|
2
|
+
`,g-1)+1,x=o.indexOf(`
|
|
3
|
+
`,g),S=x===-1?o.slice(T):o.slice(T,x+1);if(navigator.clipboard.writeText(S),t.key==="x"){const z=o.slice(0,T),P=x===-1?"":o.slice(x+1);s.value=z+P,s.selectionStart=s.selectionEnd=T,s.dispatchEvent(new Event("input",{bubbles:!0})),E.toast("Line cut",{type:"info",duration:1500})}else E.toast("Line copied",{type:"info",duration:1500})}}),e.find("#save-settings-btn").on("click",async()=>{const t=e.find("#field-admin-theme").val(),s=!l.baseTheme&&e.find("#field-auto-theme-enabled").prop("checked"),n={enabled:s,dayTheme:e.find("#field-day-theme").val()||"charcoal-light",nightTheme:e.find("#field-night-theme").val()||"charcoal-dark",dayStart:e.find("#field-day-start").val()||"07:00",nightStart:e.find("#field-night-start").val()||"19:00"},o={title:e.find("#field-site-title").val().trim(),tagline:e.find("#field-tagline").val().trim(),fontFamily:e.find("#field-font-family").val()||"Roboto",fontSize:parseInt(e.find("#field-font-size").val(),10)||16,theme:l.baseTheme?l.theme:s?n.dayTheme:e.find("#field-theme").val(),...l.baseTheme?{baseTheme:l.baseTheme}:{},autoTheme:n,adminTheme:t,layoutOptions:{spacerSize:parseInt(e.find("#field-spacer-size").val(),10)||40},seo:{defaultTitle:e.find("#field-seo-title").val().trim(),titleSeparator:e.find("#field-seo-separator").val()||" | ",defaultDescription:e.find("#field-seo-desc").val().trim()},footer:{copyright:e.find("#field-footer-copy").val().trim(),links:l.footer?.links||[]},social:{twitter:e.find("#field-social-twitter").val().trim(),facebook:e.find("#field-social-facebook").val().trim(),instagram:e.find("#field-social-instagram").val().trim(),linkedin:e.find("#field-social-linkedin").val().trim(),github:e.find("#field-social-github").val().trim(),youtube:e.find("#field-social-youtube").val().trim()},smtp:{host:e.find("#field-smtp-host").val().trim(),port:parseInt(e.find("#field-smtp-port").val(),10)||587,user:e.find("#field-smtp-user").val().trim(),pass:e.find("#field-smtp-pass").val(),secure:e.find("#field-smtp-secure").prop("checked"),fromAddress:e.find("#field-smtp-from-address").val().trim(),fromName:e.find("#field-smtp-from-name").val().trim()},backToTop:{enabled:e.find("#field-btt-enabled").prop("checked"),scrollThreshold:parseInt(e.find("#field-btt-threshold").val(),10)||300,position:e.find("#field-btt-position").val()||"bottom-right",offset:parseInt(e.find("#field-btt-offset").val(),10)||32,bottomOffset:parseInt(e.find("#field-btt-bottom-offset").val(),10)||32,label:e.find("#field-btt-label").val().trim(),smooth:e.find("#field-btt-smooth").prop("checked")},cookieConsent:{enabled:e.find("#field-cc-enabled").prop("checked"),message:e.find("#field-cc-message").val().trim(),acceptAllText:e.find("#field-cc-accept-all").val().trim(),rejectAllText:e.find("#field-cc-reject-all").val().trim(),customizeText:e.find("#field-cc-customize").val().trim(),savePreferencesText:e.find("#field-cc-save-prefs").val().trim(),privacyPolicyText:e.find("#field-cc-privacy-text").val().trim(),privacyPolicyUrl:e.find("#field-cc-privacy-url").val().trim(),cookiePolicyText:e.find("#field-cc-cookie-text").val().trim(),cookiePolicyUrl:e.find("#field-cc-cookie-url").val().trim(),position:e.find("#field-cc-position").val(),layout:e.find("#field-cc-layout").val(),theme:e.find("#field-cc-theme").val(),showFunctional:e.find("#field-cc-show-functional").prop("checked"),showAnalytics:e.find("#field-cc-show-analytics").prop("checked"),showMarketing:e.find("#field-cc-show-marketing").prop("checked"),consentVersion:e.find("#field-cc-version").val().trim()||"1.0"}},g=e.find("#save-settings-btn");g.prop("disabled",!0);try{await C.settings.save(o),Domma.theme.set(t),E.toast("Settings saved.",{type:"success"})}catch{E.toast("Failed to save settings.",{type:"error"})}finally{g.prop("disabled",!1)}}),e.find("#send-test-email-btn").on("click",async()=>{const t=e.find("#field-test-email-to").val().trim(),s=e.find("#test-email-result").get(0),n=e.find("#send-test-email-btn");n.prop("disabled",!0),s&&(s.textContent="Sending\u2026",s.style.color="");try{const o=await v("/settings/test-email",{method:"POST",body:JSON.stringify({to:t||void 0})});s&&(s.textContent=o.message||"Test email sent.",s.style.color="var(--success,#4ade80)")}catch(o){s&&(s.textContent=o.message||"Failed to send test email.",s.style.color="var(--danger,#f87171)")}finally{n.prop("disabled",!1)}}),e.find("#save-breadcrumbs-btn").on("click",async()=>{const t=e.find("#save-breadcrumbs-btn");t.prop("disabled",!0);try{const s={enabled:e.find("#field-breadcrumbs-enabled").prop("checked"),homeLabel:e.find("#field-breadcrumbs-home-label").val().trim()||"Home",position:e.find("#field-breadcrumbs-position").val()||"TL",offsetX:parseInt(e.find("#field-bc-offset-x").val(),10)||8,offsetY:parseInt(e.find("#field-bc-offset-y").val(),10)||8};await C.settings.save({...l,breadcrumbs:s}),l.breadcrumbs=s,E.toast("Breadcrumbs settings saved.",{type:"success"})}catch{E.toast("Failed to save breadcrumbs settings.",{type:"error"})}finally{t.prop("disabled",!1)}}),e.find("#save-css-btn").on("click",async()=>{const t=a?a.getValue():e.find("#field-custom-css").val(),s=e.find("#save-css-btn");s.prop("disabled",!0);try{await v("/settings/custom-css",{method:"PUT",body:JSON.stringify({css:t})}),E.toast("Custom CSS saved.",{type:"success"})}catch(n){E.toast(n.message||"Failed to save CSS.",{type:"error"})}finally{s.prop("disabled",!1)}});async function r(){try{const t=await v("/cache/status"),s=t.enabled?`enabled (${t.driver})`:"disabled",n=t.maxItems?` \xB7 ${t.size} / ${t.maxItems} entries`:"";e.find("#cache-status").text(s+n)}catch{e.find("#cache-status").text("unknown")}try{const{entries:t}=await v("/cache/keys");A(e.find("#cache-keys-table"),t),e.find("#cache-key-count").text(`(${t.length})`)}catch(t){e.find("#cache-keys-table").get(0).textContent="Failed to load keys: "+t.message}}await r(),e.find("#btn-clear-cache").on("click",async()=>{if(!await E.confirm("Clear the entire response cache?"))return;const t=e.find("#btn-clear-cache");t.prop("disabled",!0);try{await v("/cache/clear",{method:"POST"}),E.toast("Cache cleared.",{type:"success"}),await r()}catch(s){E.toast(s.message||"Failed to clear cache.",{type:"error"})}finally{t.prop("disabled",!1)}}),e.find("#btn-refresh-cache-keys").on("click",r)}};function A(e,k){const l=e.get(0);if(!l)return;for(;l.firstChild;)l.removeChild(l.firstChild);if(!k.length){const f=document.createElement("p");f.className="text-muted",f.style.fontSize=".875rem",f.textContent="Cache is empty.",l.appendChild(f);return}const p=[...k].reverse(),h=document.createElement("table");h.className="table table-compact";const b=document.createElement("thead"),y=document.createElement("tr");for(const f of["Key","Tags","Expires"]){const i=document.createElement("th");i.textContent=f,y.appendChild(i)}b.appendChild(y),h.appendChild(b);const u=document.createElement("tbody");for(const f of p){const i=document.createElement("tr"),d=document.createElement("td");d.style.fontFamily="monospace",d.style.fontSize="12px",d.textContent=f.key,i.appendChild(d);const m=document.createElement("td");m.style.fontSize="12px";for(const a of f.tags){const r=document.createElement("span");r.className="badge",r.style.marginRight="4px",r.style.fontSize="11px",r.textContent=a,m.appendChild(r)}i.appendChild(m);const c=document.createElement("td");if(c.style.fontSize="12px",c.style.color="var(--dm-muted, #6b7280)",f.expiresAt===null)c.textContent="no expiry";else{const a=f.expiresAt-Date.now();a<=0?c.textContent="expired":a<6e4?c.textContent=`${Math.round(a/1e3)}s`:a<36e5?c.textContent=`${Math.round(a/6e4)}m`:c.textContent=`${Math.round(a/36e5)}h`}i.appendChild(c),u.appendChild(i)}h.appendChild(u),l.appendChild(h)}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export const tutorialsView={templateUrl:"/admin/js/templates/tutorials.html",async onMount(t){E.tabs(t.find("#tutorials-tabs").get(0)),Domma.icons.scan(),Domma.syntax.scan()}};
|
|
1
|
+
import{mountScaffolder as a}from"../lib/crud-tutorial.js";export const tutorialsView={templateUrl:"/admin/js/templates/tutorials.html",async onMount(t){E.tabs(t.find("#tutorials-tabs").get(0));const o=t.find("#tutorial-scaffolder-mount").get(0);o&&a(o,null,null),Domma.icons.scan(),Domma.syntax.scan()}};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{api as
|
|
1
|
+
import{api as l}from"../api.js";function B(d){return{string:"string",email:"email",tel:"string",number:"number",textarea:"textarea",select:"select",radio:"select",checkbox:"boolean","checkbox-group":"select",date:"string",time:"string",url:"string",hidden:"string"}[d]||"string"}export const userEditorView={templateUrl:"/admin/js/templates/user-editor.html",async onMount(d){const y=window.location.hash.match(/#\/users\/edit\/([^/]+)/),u=y?y[1]:null,r=!!u,[o,b,g,x]=await Promise.all([r?l.users.get(u).catch(()=>null):Promise.resolve(null),l.collections.listEntries("roles",{limit:100}).catch(()=>null),l.collections.get("user-profiles").catch(()=>null),l.projects.list().catch(()=>[])]);if(r&&!o){E.toast("User not found.",{type:"error"}),R.navigate("/users");return}d.find("#editor-title").text(r?"Edit User":"New User"),d.find("#cancel-btn").on("click",()=>R.navigate("/users"));const w=(b?.entries||[]).map(e=>({value:e.data.name,label:e.data.label})).sort((e,m)=>{const t=n=>b.entries.find(a=>a.data.name===n)?.data?.level??99;return t(e.value)-t(m.value)}),k=[{value:"admin",label:"Admin"},{value:"manager",label:"Manager"},{value:"editor",label:"Editor"},{value:"subscriber",label:"Subscriber"}],v=w.length>0?w:k,j=(x||[]).map(e=>({value:e.slug,label:e.name||e.slug})),C=[{value:"",label:"(none)"},...j],O={name:{type:"string",required:!0,minLength:2,label:"Full Name",formConfig:{placeholder:"Jane Smith"}},email:{type:"email",required:!0,label:"Email Address",formConfig:{placeholder:"jane@example.com"}},role:{type:"select",required:!0,label:"Primary role",options:v,formConfig:{tooltip:"Shown as the user's badge. Used for hierarchy comparisons."}},additionalRoles:{type:"multiselect",label:"Additional roles",options:v,formConfig:{placeholder:"",tooltip:"Optional \u2014 extra roles whose permissions also apply. Useful for someone who is both, say, a recruiter AND a content editor."}},ownedByProject:{type:"select",label:"Owned by project",options:C,formConfig:{tooltip:"Administrative tag \u2014 controls where this user appears in the sidebar Projects section. Mapped to meta.project."}},projects:{type:"multiselect",label:"Project access",options:j,formConfig:{placeholder:"",tooltip:"Access scope \u2014 limits which artefacts the user can see and edit. Leave empty for no restriction."}},isActive:{type:"boolean",label:"Active account"},password:{type:"password",required:!r,minLength:r?0:8,label:r?"New Password":"Password",formConfig:{placeholder:"\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022",autocomplete:"new-password",tooltip:r?"Leave blank to keep current password":"Minimum 8 characters"}}},S=o?{name:o.name||"",email:o.email||"",role:o.role||"editor",additionalRoles:Array.isArray(o.additionalRoles)?o.additionalRoles:[],ownedByProject:o.meta?.project||"",projects:Array.isArray(o.projects)?o.projects:[],isActive:o.isActive!==!1}:{role:"editor",additionalRoles:[],ownedByProject:"",projects:[],isActive:!0};F.render("#user-form-container",O,S,{layout:"stacked",submitText:r?"Update User":"Create User",onSubmit:async e=>{if(r&&e.password&&e.password.length<8)return E.toast("Password must be at least 8 characters.",{type:"warning"}),!1;const t=(Array.isArray(e.additionalRoles)?e.additionalRoles:[]).filter(s=>s&&s!==e.role).filter((s,p,i)=>i.indexOf(s)===p),a=(Array.isArray(e.projects)?e.projects:[]).filter(Boolean).filter((s,p,i)=>i.indexOf(s)===p),c=e.ownedByProject||null,f={name:e.name,email:e.email,role:e.role,additionalRoles:t,projects:a,meta:{...o?.meta||{},project:c},isActive:!!e.isActive};e.password&&(f.password=e.password);try{let s;r?s=await l.users.update(u,f):s=await l.users.create(f);const p=g?.fields||[];if(p.length>0){const i={};for(const h of p)h.name in e&&(i[h.name]=e[h.name]);const P=r?u:s?.id;P&&Object.keys(i).length>0&&await l.users.update(P,{profile:i})}E.toast("User saved successfully.",{type:"success"}),R.navigate("/users")}catch(s){return E.toast(`Save failed: ${s.message||"Unknown error"}`,{type:"error"}),!1}}});const A=g?.fields||[];if(A.length>0){d.find("#profile-card").show();const e={},m={};for(const t of A){const a={type:B(t.type),label:t.label||t.name};t.required&&(a.required=!0),t.placeholder&&(a.formConfig={placeholder:t.placeholder}),t.options&&Array.isArray(t.options)&&(a.options=t.options.map(c=>typeof c=="string"?{value:c,label:c}:c)),e[t.name]=a,m[t.name]=o?.profile?.[t.name]??""}F.render("#profile-form-container",e,m,{layout:"stacked",submitText:r?"Update Profile":"Save Profile",onSubmit:async t=>{const n=u;if(!n)return E.toast("Save the user first, then update the profile.",{type:"info"}),!1;try{await l.users.update(n,{profile:t}),E.toast("Profile saved.",{type:"success"})}catch(a){return E.toast(`Profile save failed: ${a.message||"Unknown error"}`,{type:"error"}),!1}}})}Domma.icons.scan()}};
|
package/admin/js/views/users.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{api as
|
|
1
|
+
import{api as d,getUser as k}from"../api.js";import{filterByProject as M,getProjectFromHash as A}from"../lib/project-context.js";export const usersView={templateUrl:"/admin/js/templates/users.html",async onMount(r){const m=k(),y=E.loader(r.get(0),{type:"dots"});let[n,f]=await Promise.all([d.users.list().catch(()=>[]),d.get("/collections/roles/entries?limit=100").catch(()=>({entries:[]}))]);y.destroy();const u=A();u&&(n=M(n,u));const c=f.entries||[],b=l=>{T.create("#users-table",{data:l,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",g=a?.data?.label||e,p=`<span class="badge ${i}">${g}</span>`,o=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 p+o}},{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(o=>[o.data?.name,o.data?.level??99]));return(i[m?.role]??99)<(i[t.role]??99)?`
|
|
2
2
|
<a href="#/users/edit/${t.id}" class="btn btn-sm btn-primary">Edit</a>
|
|
3
|
-
${
|
|
4
|
-
`:""}}],emptyMessage:"No users found."}),Domma.icons.scan()};
|
|
3
|
+
${a?"":`<button class="btn btn-sm btn-danger btn-delete-user" data-id="${t.id}" data-name="${j(t.name)}">Delete</button>`}
|
|
4
|
+
`:""}}],emptyMessage:"No users found."}),Domma.icons.scan()};b(n),r.off("click",".btn-delete-user").on("click",".btn-delete-user",async function(){const l=$(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/${l}`),E.toast("User deleted.",{type:"success"}),n=n.filter(a=>a.id!==l),b(n)}catch(a){E.toast(`Failed to delete: ${a.message}`,{type:"error"})}})}};function j(r){return String(r).replace(/"/g,""")}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{api as v}from"../api.js";let g=null,f=null;export const viewEditorView={templateUrl:"/admin/js/templates/view-editor.html",async onMount(e){g=null,f=null;const t=window.location.hash.match(/\/views\/edit\/([^/?#]+)/);t&&(g=t[1]),E.tabs(e.find("#view-editor-tabs").get(0)),await T(e),await D(e),await O(e),await L(e),e.find("#view-source").get(0)?.addEventListener("change",async()=>{await N(e)}),g&&(e.find("#view-editor-title").text("Edit View"),await _(e,g)),e.find("#add-stage-btn").off("click").on("click",()=>{const n=e.find("#add-stage-type").val()||"$match";$(e,{type:n,config:{}})}),e.find("#save-view-btn").off("click").on("click",async()=>{await P(e)}),H(e),G(e),Domma.icons.scan()}};async function T(e){const t=e.find("#view-source").get(0);if(t)try{(await v.collections.list()).forEach(n=>{const a=document.createElement("option");a.value=n.slug,a.textContent=`${n.title} (${n.slug})`,t.appendChild(a)})}catch{}}async function L(e){const t=e.find("#view-block-name").get(0);if(t)try{(await v.blocks.list()).forEach(n=>{const a=document.createElement("option");a.value=n.name,a.textContent=`${n.name}.html`,t.appendChild(a)})}catch{}}async function D(e){const t=e.find("#view-connection").get(0);if(t)try{const n=await v.collections.getConnections();Object.keys(n).forEach(a=>{if(!t.querySelector(`option[value="${a}"]`)){const l=document.createElement("option");l.value=a,l.textContent=a,t.appendChild(l)}})}catch{}}async function O(e){const t=e.find("#view-roles-checkboxes").get(0);t&&["admin","manager","editor","subscriber"].forEach(n=>{const a=document.createElement("label");a.style.cssText="display:flex;align-items:center;gap:.5rem;cursor:pointer;";const l=document.createElement("input");l.type="checkbox",l.value=n,l.dataset.role=n,l.className="view-role-cb",l.checked=n==="admin",a.appendChild(l),a.appendChild(document.createTextNode(n)),t.appendChild(a)})}async function N(e){const t=e.find("#view-source").val();if(!t){f=null;return}try{f=(await v.collections.get(t))?.fields||[]}catch{f=null}e.find(".stage-card[data-guided]").each(function(){const n=this.dataset.stageType,a=this.querySelector(".stage-guided");a&&(n==="$match"&&V(a),n==="$sort"&&I(a))}),q(e,null)}async function _(e,t){try{const n=await v.views.get(t);if(!n){E.toast("View not found.",{type:"error"}),R.navigate("/views");return}const a=n.pipeline?.source;a&&(e.find("#view-source").val(a),await N(e)),j(e,n)}catch(n){E.toast(n.message||"Failed to load view.",{type:"error"}),R.navigate("/views")}}function j(e,t){e.find("#view-title").val(t.title||""),e.find("#view-slug").val(t.slug||""),e.find("#view-description").val(t.description||""),e.find("#view-source").val(t.pipeline?.source||""),e.find("#view-bundled").prop("checked",!!t.bundled),e.find("#view-connection").val(t.connection||"default"),e.find("#view-display-mode").val(t.display?.mode||"table"),e.find("#view-page-size").val(t.display?.pageSize||25),e.find("#view-block-name").val(t.display?.block||"");const n=t.access?.roles||["admin"];e.find(".view-role-cb").each(function(){this.checked=n.includes(this.value)}),e.find("#view-public").prop("checked",t.access?.public||!1);const a=t.access?.rowLevel;a&&(e.find("#view-rowlevel-enabled").prop("checked",!0),e.find("#view-rowlevel-config").css("display","flex"),e.find("#view-rowlevel-mode").val(a.mode||"owner"),e.find("#view-rowlevel-userkey").val(a.userKey||"id"),a.mode==="field"&&(e.find("#view-rowlevel-field-group").css("display",""),e.find("#view-rowlevel-field").val(a.field||"")));const l=e.find("#pipeline-stages-list").get(0);if(l)for(;l.firstChild;)l.removeChild(l.firstChild);(t.pipeline?.stages||[]).forEach(i=>$(e,i)),q(e,t.display?.columns||[]),x(e)}const z=[{value:"eq",label:"= equals"},{value:"ne",label:"\u2260 not equals"},{value:"gt",label:"> greater than"},{value:"lt",label:"< less than"},{value:"gte",label:"\u2265 greater or equal"},{value:"lte",label:"\u2264 less or equal"},{value:"contains",label:"~ contains (regex)"},{value:"in",label:"\u2208 in (comma list)"}];function y(e=!1){const t=[];return e&&t.push({value:"",label:"\u2014 select field \u2014"}),(f||[]).forEach(n=>{t.push({value:`data.${n.name}`,label:`${n.label||n.name} (${n.name})`})}),t.length===0||t.length===1&&e?(t.push({value:"__meta.createdAt",label:"Created At"}),t.push({value:"__meta.updatedAt",label:"Updated At"})):(t.push({value:"__meta.createdAt",label:"Created At (meta)"}),t.push({value:"__meta.updatedAt",label:"Updated At (meta)"})),t}function h(e,t){const n=document.createElement("select");return n.className="form-input form-input--sm",e.forEach(a=>{const l=document.createElement("option");l.value=a.value,l.textContent=a.label,String(a.value)===String(t)&&(l.selected=!0),n.appendChild(l)}),n}function k(e,t,n="text"){const a=document.createElement("input");return a.type=n,a.className="form-input form-input--sm",a.placeholder=e,a.value=t??"",a}function F(e){if(!f?.length)return null;const t=e?.startsWith("data.")?e.slice(5):null;return t&&f.find(n=>n.name===t)||null}function b(e,t,n=""){const a=F(e);if(a?.type==="select"&&(t==="eq"||t==="ne")&&a.options?.length){const d=document.createElement("select");d.className="form-input form-input--sm match-val",d.style.flex="1";const c=document.createElement("option");return c.value="",c.textContent="\u2014 select value \u2014",d.appendChild(c),(a.options||[]).forEach(o=>{const s=typeof o=="string"?o:o.value??"",r=typeof o=="string"?o:o.label||s;if(!s||s==="undefined")return;const u=document.createElement("option");u.value=s,u.textContent=r,s===n&&(u.selected=!0),d.appendChild(u)}),d}const l=t==="in"?"val1, val2, val3":t==="contains"?"search pattern (regex)":t==="gt"||t==="lt"||t==="gte"||t==="lte"?"0":"value",i=document.createElement("input");return i.type="text",i.className="form-input form-input--sm match-val",i.style.flex="1",i.placeholder=l,i.value=n,i}function S(e){const t=[],n={$eq:"eq",$ne:"ne",$gt:"gt",$lt:"lt",$gte:"gte",$lte:"lte",$in:"in"};return Object.entries(e||{}).forEach(([a,l])=>{if(typeof l=="object"&&l!==null&&!Array.isArray(l)){if("$regex"in l){t.push({field:a,op:"contains",val:String(l.$regex??"")});return}Object.entries(l).forEach(([i,d])=>{const c=n[i];c&&t.push({field:a,op:c,val:Array.isArray(d)?d.join(", "):String(d)})})}else t.push({field:a,op:"eq",val:String(l??"")})}),t}function C(e,t,n="",a="eq",l=""){const i=document.createElement("div");i.className="match-condition-row",i.style.cssText="display:flex;gap:.4rem;align-items:center;margin-bottom:.4rem;flex-wrap:wrap;";const d=h([{value:"",label:"\u2014 field \u2014"},...y()],n);d.className+=" match-field",d.style.minWidth="140px";const c=h(z,a);c.className+=" match-op",c.style.minWidth="160px";const o=document.createElement("div");o.className="match-val-wrap",o.style.cssText="flex:1;min-width:120px;display:flex;",o.appendChild(b(n,a,l));const s=()=>{const m=o.querySelector(".match-val")?.value??"";for(;o.firstChild;)o.removeChild(o.firstChild);o.appendChild(b(d.value,c.value,m))};d.addEventListener("change",s),c.addEventListener("change",s);const r=document.createElement("button");r.type="button",r.className="btn btn-sm btn-ghost",r.title="Remove";const u=document.createElement("span");u.setAttribute("data-icon","x"),r.appendChild(u),r.addEventListener("click",()=>i.remove()),i.appendChild(d),i.appendChild(c),i.appendChild(o),i.appendChild(r),e.insertBefore(i,t),Domma.icons.scan(i)}function B(e,t={}){for(;e.firstChild;)e.removeChild(e.firstChild);const n=t.$match||t||{},a="$or"in n;let l=[];a?(n.$or||[]).forEach(r=>S(r).forEach(u=>l.push(u))):l=S(n);const i=document.createElement("div");i.style.cssText="display:flex;align-items:center;gap:.5rem;margin-bottom:.6rem;";const d=document.createElement("label");d.className="form-label",d.style.marginBottom="0",d.textContent="Match:";const c=document.createElement("select");c.className="form-input form-input--sm match-logic",c.style.width="auto",[{value:"and",label:"ALL conditions (AND)"},{value:"or",label:"ANY condition (OR)"}].forEach(r=>{const u=document.createElement("option");u.value=r.value,u.textContent=r.label,(a?"or":"and")===r.value&&(u.selected=!0),c.appendChild(u)}),i.appendChild(d),i.appendChild(c),e.appendChild(i);const o=document.createElement("button");o.type="button",o.className="btn btn-ghost btn-sm match-add-btn";const s=document.createElement("span");s.setAttribute("data-icon","plus"),o.appendChild(s),o.appendChild(document.createTextNode(" Add Condition")),o.addEventListener("click",()=>{C(e,o),Domma.icons.scan(e)}),e.appendChild(o),l.forEach(r=>C(e,o,r.field,r.op,r.val)),l.length===0&&C(e,o),Domma.icons.scan(e)}function V(e){e.querySelectorAll(".match-condition-row").forEach(t=>{const n=t.querySelector(".match-field");if(n){const d=n.value,c=h([{value:"",label:"\u2014 field \u2014"},...y()],d);c.className=n.className,c.style.cssText=n.style.cssText;const o=t.querySelector(".match-val-wrap"),s=t.querySelector(".match-op");o&&s&&c.addEventListener("change",()=>{const r=o.querySelector(".match-val")?.value??"";for(;o.firstChild;)o.removeChild(o.firstChild);o.appendChild(b(c.value,s.value,r))}),n.parentNode.replaceChild(c,n)}const a=t.querySelector(".match-val-wrap"),l=t.querySelector(".match-field")?.value,i=t.querySelector(".match-op")?.value;if(a&&l&&i){const d=a.querySelector(".match-val")?.value??"";for(;a.firstChild;)a.removeChild(a.firstChild);a.appendChild(b(l,i,d))}})}function W(e){const t=e.querySelector(".match-logic")?.value||"and",n=l=>{const i=l.querySelector(".match-field")?.value?.trim(),d=l.querySelector(".match-op")?.value,c=l.querySelector(".match-val")?.value?.trim()??"";if(!i)return null;const o=i.startsWith("__")?i.slice(2):i,s={};switch(d){case"eq":s[o]=c;break;case"ne":s[o]={$ne:c};break;case"gt":s[o]={$gt:isNaN(c)?c:Number(c)};break;case"lt":s[o]={$lt:isNaN(c)?c:Number(c)};break;case"gte":s[o]={$gte:isNaN(c)?c:Number(c)};break;case"lte":s[o]={$lte:isNaN(c)?c:Number(c)};break;case"contains":s[o]={$regex:c,$options:"i"};break;case"in":s[o]={$in:c.split(",").map(r=>r.trim()).filter(Boolean)};break}return Object.keys(s).length?s:null},a=[];return e.querySelectorAll(".match-condition-row").forEach(l=>{const i=n(l);i&&a.push(i)}),a.length===0?{}:t==="or"?{$or:a}:Object.assign({},...a)}function J(e,t={}){for(;e.firstChild;)e.removeChild(e.firstChild);const n=t.$sort||t||{},a=Object.entries(n),l=document.createElement("button");l.type="button",l.className="btn btn-ghost btn-sm";const i=document.createElement("span");i.setAttribute("data-icon","plus"),l.appendChild(i),l.appendChild(document.createTextNode(" Add Sort")),e.appendChild(l);const d=(c="",o=1)=>{const s=document.createElement("div");s.className="sort-row",s.style.cssText="display:flex;gap:.4rem;align-items:center;margin-bottom:.4rem;";const r=h([{value:"",label:"\u2014 field \u2014"},...y()],c);r.className+=" sort-field";const u=h([{value:"1",label:"\u2191 Ascending"},{value:"-1",label:"\u2193 Descending"}],String(o));u.className+=" sort-dir";const m=document.createElement("button");m.type="button",m.className="btn btn-sm btn-ghost",m.title="Remove";const p=document.createElement("span");p.setAttribute("data-icon","x"),m.appendChild(p),m.addEventListener("click",()=>{s.remove()}),s.appendChild(r),s.appendChild(u),s.appendChild(m),e.insertBefore(s,l),Domma.icons.scan(s)};l.addEventListener("click",()=>{d()}),a.forEach(([c,o])=>d(c,o)),a.length===0&&d(),Domma.icons.scan(e)}function I(e){e.querySelectorAll(".sort-row").forEach(t=>{const n=t.querySelector(".sort-field"),a=n?.value,l=h([{value:"",label:"\u2014 field \u2014"},...y()],a);l.className=n.className,n.parentNode.replaceChild(l,n)})}function M(e){const t={};return e.querySelectorAll(".sort-row").forEach(n=>{const a=n.querySelector(".sort-field")?.value?.trim(),l=parseInt(n.querySelector(".sort-dir")?.value,10)||1;if(!a)return;const i=a.startsWith("__")?a.slice(2):a;t[i]=l}),t}function $(e,t){const n=e.find("#pipeline-stages-list").get(0);if(!n)return;const a=n.querySelector(".stage-empty-placeholder");a&&a.remove();const l=["$match","$sort"].includes(t.type),i=document.createElement("div");i.className="card mb-2 stage-card",i.dataset.stageType=t.type,l&&(i.dataset.guided="true");const d=document.createElement("div");d.className="card-header",d.style.cssText="display:flex;align-items:center;gap:.5rem;";const c=document.createElement("code");c.textContent=t.type,c.style.cssText="flex:1;font-size:.85rem;";const o=document.createElement("button");o.type="button",o.className="btn btn-sm btn-danger";const s=document.createElement("span");s.setAttribute("data-icon","trash-2"),o.appendChild(s),o.addEventListener("click",()=>{i.remove(),n.querySelector(".stage-card")||n.appendChild(U())}),d.appendChild(c),d.appendChild(o);const r=document.createElement("div");if(r.className="card-body",l){const u=document.createElement("div");u.className="stage-guided",r.appendChild(u),t.type==="$match"&&B(u,t.config),t.type==="$sort"&&J(u,t.config)}else{const u=document.createElement("label");u.className="form-label",u.textContent="Stage Config (JSON)";const m=document.createElement("small");m.className="text-muted",m.style.cssText="display:block;margin-bottom:.4rem;",m.textContent=`Enter the inner config for ${t.type} \u2014 e.g. for $lookup: { from, localField, foreignField, as }. Do not wrap in { "${t.type}": ... }.`;const p=document.createElement("textarea");p.className="form-input stage-config",p.rows=5,p.style.cssText="font-family:monospace;font-size:.8rem;resize:vertical;",p.placeholder="{}",p.value=Object.keys(t.config||{}).length?JSON.stringify(t.config,null,2):"",r.appendChild(u),r.appendChild(m),r.appendChild(p)}i.appendChild(d),i.appendChild(r),n.appendChild(i),Domma.icons.scan(i)}function U(){const e=document.createElement("p");return e.className="text-muted stage-empty-placeholder",e.textContent="No stages yet. Add a stage to filter, join, or transform your data.",e.style.cssText="text-align:center;padding:2rem 0;",e}function K(e){const t=[];return e.find(".stage-card").each(function(){const n=this.dataset.stageType,a=this.querySelector(".stage-guided");let l;if(a)n==="$match"&&(l=W(a)),n==="$sort"&&(l=M(a));else{const i=this.querySelector(".stage-config")?.value?.trim()||"{}";try{l=JSON.parse(i)}catch{throw new Error(`Invalid JSON in ${n} stage config`)}}t.push({type:n,config:l})}),t}function q(e,t){const n=e.find("#view-columns-builder").get(0);if(!n)return;for(;n.firstChild;)n.removeChild(n.firstChild);const a=t??(f||[]).map(c=>({key:`data.${c.name}`,label:c.label||c.name})),l=(c="",o="")=>{const s=document.createElement("div");s.className="col-row",s.style.cssText="display:flex;gap:.4rem;align-items:center;margin-bottom:.4rem;";const r=f?.length?h([{value:"",label:"\u2014 field \u2014"},...y()],c):k("data.fieldName",c);r.className+=" col-key";const u=k("Display label",o);u.className+=" col-label",u.style.flex="1",f?.length&&r.tagName==="SELECT"&&r.addEventListener("change",()=>{if(!u.value){const w=(f||[]).find(A=>`data.${A.name}`===r.value);w&&(u.value=w.label||w.name)}});const m=document.createElement("button");m.type="button",m.className="btn btn-sm btn-ghost",m.title="Remove column";const p=document.createElement("span");p.setAttribute("data-icon","x"),m.appendChild(p),m.addEventListener("click",()=>{s.remove(),Domma.icons.scan(n)}),s.appendChild(r),s.appendChild(u),s.appendChild(m),n.insertBefore(s,n.lastChild),Domma.icons.scan(s)};a.forEach(c=>l(c.key,c.label));const i=document.createElement("button");i.type="button",i.className="btn btn-ghost btn-sm";const d=document.createElement("span");d.setAttribute("data-icon","plus"),i.appendChild(d),i.appendChild(document.createTextNode(" Add Column")),i.addEventListener("click",()=>{l(),Domma.icons.scan(n)}),n.appendChild(i),Domma.icons.scan(n)}function Y(e){const t=[];return e.find(".col-row").each(function(){const n=this.querySelector(".col-key")?.value?.trim(),a=this.querySelector(".col-label")?.value?.trim();n&&t.push({key:n,label:a||n})}),t}function G(e){const t=e.find("#view-display-mode").get(0);t&&(t.addEventListener("change",()=>x(e)),x(e))}function x(e){const t=e.find("#view-display-mode").val()||"table",n=e.find("#view-columns-section").get(0),a=e.find("#view-block-section").get(0);n&&(n.style.display=t==="table"?"":"none"),a&&(a.style.display=t==="block"?"":"none")}function H(e){const t=e.find("#view-rowlevel-enabled").get(0),n=e.find("#view-rowlevel-config").get(0),a=e.find("#view-rowlevel-mode").get(0),l=e.find("#view-rowlevel-field-group").get(0);t&&(t.addEventListener("change",()=>{n&&(n.style.display=t.checked?"flex":"none")}),a&&a.addEventListener("change",()=>{l&&(l.style.display=a.value==="field"?"":"none")}))}async function P(e){const t=e.find("#view-title").val().trim();if(!t){E.toast("Title is required.",{type:"warning"});return}const n=e.find("#view-source").val();if(!n){E.toast("Source collection is required (Source tab).",{type:"warning"});return}let a;try{a=K(e)}catch(r){E.toast(r.message,{type:"error"});return}const l=Y(e),i=[];e.find(".view-role-cb:checked").each(function(){i.push(this.value)});const d=e.find("#view-rowlevel-enabled").is(":checked");let c=null;if(d){const r=e.find("#view-rowlevel-mode").val()||"owner",u=e.find("#view-rowlevel-userkey").val()||"id";if(c={mode:r,userKey:u},r==="field"){const m=e.find("#view-rowlevel-field").val().trim();if(!m){E.toast("Field name is required for Field Match mode.",{type:"warning"});return}c.field=m}}const o={title:t,slug:e.find("#view-slug").val().trim()||void 0,description:e.find("#view-description").val().trim(),...e.find("#view-bundled").is(":checked")?{bundled:!0}:{},connection:e.find("#view-connection").val()||"default",pipeline:{source:n,stages:a},display:{mode:e.find("#view-display-mode").val()||"table",columns:l,pageSize:parseInt(e.find("#view-page-size").val(),10)||25,block:e.find("#view-block-name").val()||""},access:{roles:i,public:e.find("#view-public").is(":checked"),rowLevel:c}},s=e.find("#save-view-btn").get(0);s&&(s.disabled=!0);try{if(g)await v.views.update(g,o),E.toast("View updated.",{type:"success"});else{const r=await v.views.create(o);E.toast("View created.",{type:"success"}),R.navigate(`/views/edit/${r.slug}`)}}catch(r){E.toast(r.message||"Failed to save view.",{type:"error"})}finally{s&&(s.disabled=!1)}}
|
|
1
|
+
import{api as v}from"../api.js";let g=null,f=null,C={};async function T(e){const t=e.find("#view-project").get(0);if(t)try{(await v.projects.list()).forEach(a=>{const l=document.createElement("option");l.value=a.slug,l.textContent=a.name||a.slug,t.appendChild(l)})}catch{}}export const viewEditorView={templateUrl:"/admin/js/templates/view-editor.html",async onMount(e){g=null,f=null,C={};const t=window.location.hash.match(/\/views\/edit\/([^/?#]+)/);t&&(g=t[1]),E.tabs(e.find("#view-editor-tabs").get(0)),await L(e),await _(e),await O(e),await D(e),await T(e),e.find("#view-source").get(0)?.addEventListener("change",async()=>{await k(e)}),g&&(e.find("#view-editor-title").text("Edit View"),await V(e,g)),e.find("#add-stage-btn").off("click").on("click",()=>{const n=e.find("#add-stage-type").val()||"$match";q(e,{type:n,config:{}})}),e.find("#save-view-btn").off("click").on("click",async()=>{await X(e)}),Q(e),H(e),Domma.icons.scan()}};async function L(e){const t=e.find("#view-source").get(0);if(t)try{(await v.collections.list()).forEach(n=>{const a=document.createElement("option");a.value=n.slug,a.textContent=`${n.title} (${n.slug})`,t.appendChild(a)})}catch{}}async function D(e){const t=e.find("#view-block-name").get(0);if(t)try{(await v.blocks.list()).forEach(n=>{const a=document.createElement("option");a.value=n.name,a.textContent=`${n.name}.html`,t.appendChild(a)})}catch{}}async function _(e){const t=e.find("#view-connection").get(0);if(t)try{const n=await v.collections.getConnections();Object.keys(n).forEach(a=>{if(!t.querySelector(`option[value="${a}"]`)){const l=document.createElement("option");l.value=a,l.textContent=a,t.appendChild(l)}})}catch{}}async function O(e){const t=e.find("#view-roles-checkboxes").get(0);t&&["admin","manager","editor","subscriber"].forEach(n=>{const a=document.createElement("label");a.style.cssText="display:flex;align-items:center;gap:.5rem;cursor:pointer;";const l=document.createElement("input");l.type="checkbox",l.value=n,l.dataset.role=n,l.className="view-role-cb",l.checked=n==="admin",a.appendChild(l),a.appendChild(document.createTextNode(n)),t.appendChild(a)})}async function k(e){const t=e.find("#view-source").val();if(!t){f=null;return}try{f=(await v.collections.get(t))?.fields||[]}catch{f=null}e.find(".stage-card[data-guided]").each(function(){const n=this.dataset.stageType,a=this.querySelector(".stage-guided");a&&(n==="$match"&&J(a),n==="$sort"&&U(a))}),A(e,null)}async function V(e,t){try{const n=await v.views.get(t);if(!n){E.toast("View not found.",{type:"error"}),R.navigate("/views");return}const a=n.pipeline?.source;a&&(e.find("#view-source").val(a),await k(e)),z(e,n)}catch(n){E.toast(n.message||"Failed to load view.",{type:"error"}),R.navigate("/views")}}function z(e,t){e.find("#view-title").val(t.title||""),e.find("#view-slug").val(t.slug||""),e.find("#view-description").val(t.description||""),e.find("#view-source").val(t.pipeline?.source||""),e.find("#view-bundled").prop("checked",!!t.bundled),C=t.meta||{},e.find("#view-project").val(t.meta?.project||""),e.find("#view-connection").val(t.connection||"default"),e.find("#view-display-mode").val(t.display?.mode||"table"),e.find("#view-page-size").val(t.display?.pageSize||25),e.find("#view-block-name").val(t.display?.block||"");const n=t.access?.roles||["admin"];e.find(".view-role-cb").each(function(){this.checked=n.includes(this.value)}),e.find("#view-public").prop("checked",t.access?.public||!1);const a=t.access?.rowLevel;a&&(e.find("#view-rowlevel-enabled").prop("checked",!0),e.find("#view-rowlevel-config").css("display","flex"),e.find("#view-rowlevel-mode").val(a.mode||"owner"),e.find("#view-rowlevel-userkey").val(a.userKey||"id"),a.mode==="field"&&(e.find("#view-rowlevel-field-group").css("display",""),e.find("#view-rowlevel-field").val(a.field||"")));const l=e.find("#pipeline-stages-list").get(0);if(l)for(;l.firstChild;)l.removeChild(l.firstChild);(t.pipeline?.stages||[]).forEach(i=>q(e,i)),A(e,t.display?.columns||[]),N(e)}const F=[{value:"eq",label:"= equals"},{value:"ne",label:"\u2260 not equals"},{value:"gt",label:"> greater than"},{value:"lt",label:"< less than"},{value:"gte",label:"\u2265 greater or equal"},{value:"lte",label:"\u2264 less or equal"},{value:"contains",label:"~ contains (regex)"},{value:"in",label:"\u2208 in (comma list)"}];function y(e=!1){const t=[];return e&&t.push({value:"",label:"\u2014 select field \u2014"}),(f||[]).forEach(n=>{t.push({value:`data.${n.name}`,label:`${n.label||n.name} (${n.name})`})}),t.length===0||t.length===1&&e?(t.push({value:"__meta.createdAt",label:"Created At"}),t.push({value:"__meta.updatedAt",label:"Updated At"})):(t.push({value:"__meta.createdAt",label:"Created At (meta)"}),t.push({value:"__meta.updatedAt",label:"Updated At (meta)"})),t}function h(e,t){const n=document.createElement("select");return n.className="form-input form-input--sm",e.forEach(a=>{const l=document.createElement("option");l.value=a.value,l.textContent=a.label,String(a.value)===String(t)&&(l.selected=!0),n.appendChild(l)}),n}function S(e,t,n="text"){const a=document.createElement("input");return a.type=n,a.className="form-input form-input--sm",a.placeholder=e,a.value=t??"",a}function B(e){if(!f?.length)return null;const t=e?.startsWith("data.")?e.slice(5):null;return t&&f.find(n=>n.name===t)||null}function b(e,t,n=""){const a=B(e);if(a?.type==="select"&&(t==="eq"||t==="ne")&&a.options?.length){const d=document.createElement("select");d.className="form-input form-input--sm match-val",d.style.flex="1";const c=document.createElement("option");return c.value="",c.textContent="\u2014 select value \u2014",d.appendChild(c),(a.options||[]).forEach(o=>{const s=typeof o=="string"?o:o.value??"",r=typeof o=="string"?o:o.label||s;if(!s||s==="undefined")return;const u=document.createElement("option");u.value=s,u.textContent=r,s===n&&(u.selected=!0),d.appendChild(u)}),d}const l=t==="in"?"val1, val2, val3":t==="contains"?"search pattern (regex)":t==="gt"||t==="lt"||t==="gte"||t==="lte"?"0":"value",i=document.createElement("input");return i.type="text",i.className="form-input form-input--sm match-val",i.style.flex="1",i.placeholder=l,i.value=n,i}function $(e){const t=[],n={$eq:"eq",$ne:"ne",$gt:"gt",$lt:"lt",$gte:"gte",$lte:"lte",$in:"in"};return Object.entries(e||{}).forEach(([a,l])=>{if(typeof l=="object"&&l!==null&&!Array.isArray(l)){if("$regex"in l){t.push({field:a,op:"contains",val:String(l.$regex??"")});return}Object.entries(l).forEach(([i,d])=>{const c=n[i];c&&t.push({field:a,op:c,val:Array.isArray(d)?d.join(", "):String(d)})})}else t.push({field:a,op:"eq",val:String(l??"")})}),t}function x(e,t,n="",a="eq",l=""){const i=document.createElement("div");i.className="match-condition-row",i.style.cssText="display:flex;gap:.4rem;align-items:center;margin-bottom:.4rem;flex-wrap:wrap;";const d=h([{value:"",label:"\u2014 field \u2014"},...y()],n);d.className+=" match-field",d.style.minWidth="140px";const c=h(F,a);c.className+=" match-op",c.style.minWidth="160px";const o=document.createElement("div");o.className="match-val-wrap",o.style.cssText="flex:1;min-width:120px;display:flex;",o.appendChild(b(n,a,l));const s=()=>{const m=o.querySelector(".match-val")?.value??"";for(;o.firstChild;)o.removeChild(o.firstChild);o.appendChild(b(d.value,c.value,m))};d.addEventListener("change",s),c.addEventListener("change",s);const r=document.createElement("button");r.type="button",r.className="btn btn-sm btn-ghost",r.title="Remove";const u=document.createElement("span");u.setAttribute("data-icon","x"),r.appendChild(u),r.addEventListener("click",()=>i.remove()),i.appendChild(d),i.appendChild(c),i.appendChild(o),i.appendChild(r),e.insertBefore(i,t),Domma.icons.scan(i)}function W(e,t={}){for(;e.firstChild;)e.removeChild(e.firstChild);const n=t.$match||t||{},a="$or"in n;let l=[];a?(n.$or||[]).forEach(r=>$(r).forEach(u=>l.push(u))):l=$(n);const i=document.createElement("div");i.style.cssText="display:flex;align-items:center;gap:.5rem;margin-bottom:.6rem;";const d=document.createElement("label");d.className="form-label",d.style.marginBottom="0",d.textContent="Match:";const c=document.createElement("select");c.className="form-input form-input--sm match-logic",c.style.width="auto",[{value:"and",label:"ALL conditions (AND)"},{value:"or",label:"ANY condition (OR)"}].forEach(r=>{const u=document.createElement("option");u.value=r.value,u.textContent=r.label,(a?"or":"and")===r.value&&(u.selected=!0),c.appendChild(u)}),i.appendChild(d),i.appendChild(c),e.appendChild(i);const o=document.createElement("button");o.type="button",o.className="btn btn-ghost btn-sm match-add-btn";const s=document.createElement("span");s.setAttribute("data-icon","plus"),o.appendChild(s),o.appendChild(document.createTextNode(" Add Condition")),o.addEventListener("click",()=>{x(e,o),Domma.icons.scan(e)}),e.appendChild(o),l.forEach(r=>x(e,o,r.field,r.op,r.val)),l.length===0&&x(e,o),Domma.icons.scan(e)}function J(e){e.querySelectorAll(".match-condition-row").forEach(t=>{const n=t.querySelector(".match-field");if(n){const d=n.value,c=h([{value:"",label:"\u2014 field \u2014"},...y()],d);c.className=n.className,c.style.cssText=n.style.cssText;const o=t.querySelector(".match-val-wrap"),s=t.querySelector(".match-op");o&&s&&c.addEventListener("change",()=>{const r=o.querySelector(".match-val")?.value??"";for(;o.firstChild;)o.removeChild(o.firstChild);o.appendChild(b(c.value,s.value,r))}),n.parentNode.replaceChild(c,n)}const a=t.querySelector(".match-val-wrap"),l=t.querySelector(".match-field")?.value,i=t.querySelector(".match-op")?.value;if(a&&l&&i){const d=a.querySelector(".match-val")?.value??"";for(;a.firstChild;)a.removeChild(a.firstChild);a.appendChild(b(l,i,d))}})}function M(e){const t=e.querySelector(".match-logic")?.value||"and",n=l=>{const i=l.querySelector(".match-field")?.value?.trim(),d=l.querySelector(".match-op")?.value,c=l.querySelector(".match-val")?.value?.trim()??"";if(!i)return null;const o=i.startsWith("__")?i.slice(2):i,s={};switch(d){case"eq":s[o]=c;break;case"ne":s[o]={$ne:c};break;case"gt":s[o]={$gt:isNaN(c)?c:Number(c)};break;case"lt":s[o]={$lt:isNaN(c)?c:Number(c)};break;case"gte":s[o]={$gte:isNaN(c)?c:Number(c)};break;case"lte":s[o]={$lte:isNaN(c)?c:Number(c)};break;case"contains":s[o]={$regex:c,$options:"i"};break;case"in":s[o]={$in:c.split(",").map(r=>r.trim()).filter(Boolean)};break}return Object.keys(s).length?s:null},a=[];return e.querySelectorAll(".match-condition-row").forEach(l=>{const i=n(l);i&&a.push(i)}),a.length===0?{}:t==="or"?{$or:a}:Object.assign({},...a)}function I(e,t={}){for(;e.firstChild;)e.removeChild(e.firstChild);const n=t.$sort||t||{},a=Object.entries(n),l=document.createElement("button");l.type="button",l.className="btn btn-ghost btn-sm";const i=document.createElement("span");i.setAttribute("data-icon","plus"),l.appendChild(i),l.appendChild(document.createTextNode(" Add Sort")),e.appendChild(l);const d=(c="",o=1)=>{const s=document.createElement("div");s.className="sort-row",s.style.cssText="display:flex;gap:.4rem;align-items:center;margin-bottom:.4rem;";const r=h([{value:"",label:"\u2014 field \u2014"},...y()],c);r.className+=" sort-field";const u=h([{value:"1",label:"\u2191 Ascending"},{value:"-1",label:"\u2193 Descending"}],String(o));u.className+=" sort-dir";const m=document.createElement("button");m.type="button",m.className="btn btn-sm btn-ghost",m.title="Remove";const p=document.createElement("span");p.setAttribute("data-icon","x"),m.appendChild(p),m.addEventListener("click",()=>{s.remove()}),s.appendChild(r),s.appendChild(u),s.appendChild(m),e.insertBefore(s,l),Domma.icons.scan(s)};l.addEventListener("click",()=>{d()}),a.forEach(([c,o])=>d(c,o)),a.length===0&&d(),Domma.icons.scan(e)}function U(e){e.querySelectorAll(".sort-row").forEach(t=>{const n=t.querySelector(".sort-field"),a=n?.value,l=h([{value:"",label:"\u2014 field \u2014"},...y()],a);l.className=n.className,n.parentNode.replaceChild(l,n)})}function K(e){const t={};return e.querySelectorAll(".sort-row").forEach(n=>{const a=n.querySelector(".sort-field")?.value?.trim(),l=parseInt(n.querySelector(".sort-dir")?.value,10)||1;if(!a)return;const i=a.startsWith("__")?a.slice(2):a;t[i]=l}),t}function q(e,t){const n=e.find("#pipeline-stages-list").get(0);if(!n)return;const a=n.querySelector(".stage-empty-placeholder");a&&a.remove();const l=["$match","$sort"].includes(t.type),i=document.createElement("div");i.className="card mb-2 stage-card",i.dataset.stageType=t.type,l&&(i.dataset.guided="true");const d=document.createElement("div");d.className="card-header",d.style.cssText="display:flex;align-items:center;gap:.5rem;";const c=document.createElement("code");c.textContent=t.type,c.style.cssText="flex:1;font-size:.85rem;";const o=document.createElement("button");o.type="button",o.className="btn btn-sm btn-danger";const s=document.createElement("span");s.setAttribute("data-icon","trash-2"),o.appendChild(s),o.addEventListener("click",()=>{i.remove(),n.querySelector(".stage-card")||n.appendChild(P())}),d.appendChild(c),d.appendChild(o);const r=document.createElement("div");if(r.className="card-body",l){const u=document.createElement("div");u.className="stage-guided",r.appendChild(u),t.type==="$match"&&W(u,t.config),t.type==="$sort"&&I(u,t.config)}else{const u=document.createElement("label");u.className="form-label",u.textContent="Stage Config (JSON)";const m=document.createElement("small");m.className="text-muted",m.style.cssText="display:block;margin-bottom:.4rem;",m.textContent=`Enter the inner config for ${t.type} \u2014 e.g. for $lookup: { from, localField, foreignField, as }. Do not wrap in { "${t.type}": ... }.`;const p=document.createElement("textarea");p.className="form-input stage-config",p.rows=5,p.style.cssText="font-family:monospace;font-size:.8rem;resize:vertical;",p.placeholder="{}",p.value=Object.keys(t.config||{}).length?JSON.stringify(t.config,null,2):"",r.appendChild(u),r.appendChild(m),r.appendChild(p)}i.appendChild(d),i.appendChild(r),n.appendChild(i),Domma.icons.scan(i)}function P(){const e=document.createElement("p");return e.className="text-muted stage-empty-placeholder",e.textContent="No stages yet. Add a stage to filter, join, or transform your data.",e.style.cssText="text-align:center;padding:2rem 0;",e}function Y(e){const t=[];return e.find(".stage-card").each(function(){const n=this.dataset.stageType,a=this.querySelector(".stage-guided");let l;if(a)n==="$match"&&(l=M(a)),n==="$sort"&&(l=K(a));else{const i=this.querySelector(".stage-config")?.value?.trim()||"{}";try{l=JSON.parse(i)}catch{throw new Error(`Invalid JSON in ${n} stage config`)}}t.push({type:n,config:l})}),t}function A(e,t){const n=e.find("#view-columns-builder").get(0);if(!n)return;for(;n.firstChild;)n.removeChild(n.firstChild);const a=t??(f||[]).map(c=>({key:`data.${c.name}`,label:c.label||c.name})),l=(c="",o="")=>{const s=document.createElement("div");s.className="col-row",s.style.cssText="display:flex;gap:.4rem;align-items:center;margin-bottom:.4rem;";const r=f?.length?h([{value:"",label:"\u2014 field \u2014"},...y()],c):S("data.fieldName",c);r.className+=" col-key";const u=S("Display label",o);u.className+=" col-label",u.style.flex="1",f?.length&&r.tagName==="SELECT"&&r.addEventListener("change",()=>{if(!u.value){const w=(f||[]).find(j=>`data.${j.name}`===r.value);w&&(u.value=w.label||w.name)}});const m=document.createElement("button");m.type="button",m.className="btn btn-sm btn-ghost",m.title="Remove column";const p=document.createElement("span");p.setAttribute("data-icon","x"),m.appendChild(p),m.addEventListener("click",()=>{s.remove(),Domma.icons.scan(n)}),s.appendChild(r),s.appendChild(u),s.appendChild(m),n.insertBefore(s,n.lastChild),Domma.icons.scan(s)};a.forEach(c=>l(c.key,c.label));const i=document.createElement("button");i.type="button",i.className="btn btn-ghost btn-sm";const d=document.createElement("span");d.setAttribute("data-icon","plus"),i.appendChild(d),i.appendChild(document.createTextNode(" Add Column")),i.addEventListener("click",()=>{l(),Domma.icons.scan(n)}),n.appendChild(i),Domma.icons.scan(n)}function G(e){const t=[];return e.find(".col-row").each(function(){const n=this.querySelector(".col-key")?.value?.trim(),a=this.querySelector(".col-label")?.value?.trim();n&&t.push({key:n,label:a||n})}),t}function H(e){const t=e.find("#view-display-mode").get(0);t&&(t.addEventListener("change",()=>N(e)),N(e))}function N(e){const t=e.find("#view-display-mode").val()||"table",n=e.find("#view-columns-section").get(0),a=e.find("#view-block-section").get(0);n&&(n.style.display=t==="table"?"":"none"),a&&(a.style.display=t==="block"?"":"none")}function Q(e){const t=e.find("#view-rowlevel-enabled").get(0),n=e.find("#view-rowlevel-config").get(0),a=e.find("#view-rowlevel-mode").get(0),l=e.find("#view-rowlevel-field-group").get(0);t&&(t.addEventListener("change",()=>{n&&(n.style.display=t.checked?"flex":"none")}),a&&a.addEventListener("change",()=>{l&&(l.style.display=a.value==="field"?"":"none")}))}async function X(e){const t=e.find("#view-title").val().trim();if(!t){E.toast("Title is required.",{type:"warning"});return}const n=e.find("#view-source").val();if(!n){E.toast("Source collection is required (Source tab).",{type:"warning"});return}let a;try{a=Y(e)}catch(u){E.toast(u.message,{type:"error"});return}const l=G(e),i=[];e.find(".view-role-cb:checked").each(function(){i.push(this.value)});const d=e.find("#view-rowlevel-enabled").is(":checked");let c=null;if(d){const u=e.find("#view-rowlevel-mode").val()||"owner",m=e.find("#view-rowlevel-userkey").val()||"id";if(c={mode:u,userKey:m},u==="field"){const p=e.find("#view-rowlevel-field").val().trim();if(!p){E.toast("Field name is required for Field Match mode.",{type:"warning"});return}c.field=p}}const o=e.find("#view-project").val()||"",s={title:t,slug:e.find("#view-slug").val().trim()||void 0,description:e.find("#view-description").val().trim(),...e.find("#view-bundled").is(":checked")?{bundled:!0}:{},connection:e.find("#view-connection").val()||"default",pipeline:{source:n,stages:a},display:{mode:e.find("#view-display-mode").val()||"table",columns:l,pageSize:parseInt(e.find("#view-page-size").val(),10)||25,block:e.find("#view-block-name").val()||""},access:{roles:i,public:e.find("#view-public").is(":checked"),rowLevel:c},meta:{...C||{},project:o||null}},r=e.find("#save-view-btn").get(0);r&&(r.disabled=!0);try{if(g)await v.views.update(g,s),E.toast("View updated.",{type:"success"});else{const u=await v.views.create(s);E.toast("View created.",{type:"success"}),R.navigate(`/views/edit/${u.slug}`)}}catch(u){E.toast(u.message||"Failed to save view.",{type:"error"})}finally{r&&(r.disabled=!1)}}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{api as
|
|
1
|
+
import{api as l}from"../api.js";import{filterByProject as m,getProjectFromHash as p}from"../lib/project-context.js";function i(s){return String(s).replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""")}export const viewsListView={templateUrl:"/admin/js/templates/views-list.html",async onMount(s){await d(s),s.find("#create-view-btn").off("click").on("click",()=>{R.navigate("/views/new")}),Domma.icons.scan()}};async function d(s){let o=[];try{o=await l.views.list(),s.find("#views-pro-notice").hide()}catch(e){e.message?.includes("MongoDB")||e.message?.includes("pro mode")||e.message?.includes("connection")?s.find("#views-pro-notice").show():E.toast("Could not load views.",{type:"error"})}const c=p();c&&(o=m(o,c)),T.create("#views-table",{data:o,columns:[{key:"title",title:"Title",render:(e,t)=>{const n=document.createElement("a");return n.href=`#/views/${i(t.slug)}/preview`,n.textContent=e,n.style.fontWeight="600",n.outerHTML}},{key:"slug",title:"Slug",render:e=>`<code>${i(e)}</code>`},{key:"pipeline",title:"Source",render:e=>`<code>${i(e?.source||"\u2014")}</code>`},{key:"display",title:"Mode",render:e=>i(e?.mode||"table")},{key:"access",title:"Roles",render:e=>(e?.roles||[]).map(t=>`<span class="badge badge-secondary">${i(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 n=document.createElement("a");n.href=`#/views/${i(e)}/preview`,n.className="btn btn-sm btn-ghost",n.textContent="Preview";const a=document.createElement("a");a.href=`#/views/edit/${i(e)}`,a.className="btn btn-sm btn-primary",a.textContent="Edit";const r=document.createElement("button");return r.className="btn btn-sm btn-danger js-delete-view",r.dataset.slug=e,r.textContent="Delete",t.appendChild(n),t.appendChild(a),t.appendChild(r),t.outerHTML}}],emptyMessage:'No views yet. Click "New View" to create your first view.'}),document.querySelectorAll(".js-delete-view").forEach(e=>{e.addEventListener("click",async()=>{const t=e.dataset.slug;if(await E.confirm(`Delete view "${t}"? This cannot be undone.`))try{await l.views.delete(t),E.toast("View deleted.",{type:"success"}),await d(s)}catch{E.toast("Failed to delete view.",{type:"error"})}})}),Domma.icons.scan()}
|