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/forms.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{apiRequest as p}from"/admin/js/api.js";function u(
|
|
1
|
+
import{apiRequest as p}from"/admin/js/api.js";import{filterByProject as b,getProjectFromHash as h}from"/admin/js/lib/project-context.js";function u(d){return String(d).replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""").replace(/'/g,"'")}export const formsView={templateUrl:"/admin/js/templates/forms.html",async onMount(d){await g(d),d.find("#create-form-btn").off("click").on("click",()=>{const r=E.modal({title:"Create Form",size:"sm"}),l=document.createElement("div");l.style.cssText="padding:.25rem 0 .5rem;";const e=document.createElement("div");F.create({title:{type:"string",label:"Form Title",placeholder:"e.g. Contact, Feedback\u2026",required:!0}},{},{showSubmitButton:!1}).renderTo(e),l.appendChild(e);const t=document.createElement("div");t.style.cssText="display:flex;justify-content:flex-end;gap:.5rem;margin-top:.75rem;";const n=document.createElement("button");n.className="btn btn-ghost",n.textContent="Cancel";const a=document.createElement("button");a.className="btn btn-primary",a.textContent="Create",t.appendChild(n),t.appendChild(a),l.appendChild(t),r.element.appendChild(l),r.open();const s=e.querySelector('input[name="title"]');setTimeout(()=>s?.focus(),50);async function o(){const c=s?.value.trim();if(c)try{const i=await p("/forms",{method:"POST",body:JSON.stringify({title:c})});r.close(),R.navigate(`/forms/edit/${i.slug}`)}catch(i){E.toast(i.message||"Failed to create form.",{type:"error"})}}n.addEventListener("click",()=>r.close()),a.addEventListener("click",o),s?.addEventListener("keydown",c=>{c.key==="Enter"&&o()})}),d.find("#test-email-btn").off("click").on("click",async()=>{const l=(window.__CMS_SITE__?.smtp||{}).fromAddress||"",e=E.modal({title:"Send Test Email",size:"sm"}),t=document.createElement("div");t.style.cssText="padding:.25rem 0 .5rem;";const n=document.createElement("div");n.className="mb-3";const a=document.createElement("label");a.className="form-label",a.textContent="Send to";const s=document.createElement("input");s.type="email",s.className="form-input",s.value=l,s.placeholder="test@example.com",n.appendChild(a),n.appendChild(s),t.appendChild(n);const o=document.createElement("p");o.className="text-muted",o.style.cssText="font-size:.8rem;margin-bottom:.75rem;",o.textContent="SMTP settings are configured in Site Settings.",t.appendChild(o);const c=document.createElement("div");c.style.cssText="display:flex;justify-content:flex-end;gap:.5rem;margin-top:.75rem;";const i=document.createElement("button");i.className="btn btn-ghost",i.textContent="Cancel";const m=document.createElement("button");m.className="btn btn-primary",m.textContent="Send",c.appendChild(i),c.appendChild(m),t.appendChild(c),e.element.appendChild(t),e.open(),setTimeout(()=>s?.focus(),50),i.addEventListener("click",()=>e.close()),m.addEventListener("click",async()=>{const f=s.value.trim();if(f){m.disabled=!0;try{await p("/forms/test-email",{method:"POST",body:JSON.stringify({to:f})}),e.close(),E.toast("Test email sent.",{type:"success"})}catch(y){E.toast(y.message||"Failed to send test email.",{type:"error"})}finally{m.disabled=!1}}})}),Domma.icons.scan()}};async function g(d){let r=[];try{r=await p("/forms")}catch{E.toast("Could not load forms.",{type:"error"})}const l=h();l&&(r=b(r,l)),T.create("#forms-table",{data:r,columns:[{key:"title",title:"Title",render:(e,t)=>{const n=document.createElement("span");n.style.cssText="display:inline-flex;align-items:center;gap:.4rem;flex-wrap:wrap;";const a=document.createElement("a");if(a.href=`#/forms/edit/${u(t.slug)}`,a.textContent=e,a.style.fontWeight="600",n.appendChild(a),t.plugin){const s=document.createElement("span");s.className="badge badge-outline",s.textContent=t.plugin,s.title=`Managed by the ${t.plugin} plugin`,s.style.cssText="font-size:0.65rem;padding:1px 6px;color:var(--dm-warning,#d97706);border-color:var(--dm-warning,#d97706);flex-shrink:0;",n.appendChild(s)}return n.outerHTML}},{key:"slug",title:"Slug",render:e=>{const t=document.createElement("code");return t.textContent=e,t.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,t)=>{const n=document.createElement("div");n.style.cssText="display:flex;gap:.4rem;justify-content:flex-end;";const a=document.createElement("a");a.href=`#/forms/edit/${u(e)}`,a.className="btn btn-sm btn-primary",a.textContent="Edit";const s=document.createElement("a");s.href=`#/forms/${u(e)}/submissions`,s.className="btn btn-sm btn-ghost",s.textContent="Submissions";const o=document.createElement("button");return o.className="btn btn-sm btn-danger js-delete-form",o.dataset.slug=e,o.dataset.plugin=t.plugin||"",o.textContent="Delete",n.appendChild(a),n.appendChild(s),n.appendChild(o),n.outerHTML}}],emptyMessage:'No forms yet. Click "Create Form" to get started.'}),document.querySelectorAll(".js-delete-form").forEach(e=>{e.addEventListener("click",async()=>{const t=e.dataset.slug,n=e.dataset.plugin,a=n?`This form is managed by the <strong>${n}</strong> plugin. Deleting it may cause the plugin to malfunction. Continue?`:`Delete form "${t}" and all its submissions? This cannot be undone.`;if(await E.confirm(a))try{await p(`/forms/${t}`,{method:"DELETE"}),E.toast("Form deleted.",{type:"success"}),await g(d)}catch{E.toast("Failed to delete form.",{type:"error"})}})}),Domma.icons.scan()}
|
package/admin/js/views/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{dashboardView as o}from"./dashboard.js
|
|
1
|
+
import{dashboardView as o}from"./dashboard.js";import{pagesView as i}from"./pages.js";import{pageEditorView as r}from"./page-editor.js";import{settingsView as t}from"./settings.js";import{navigationView as e}from"./navigation.js";import{notificationsView as m}from"./notifications.js";import{layoutsView as s}from"./layouts.js";import{mediaView as p}from"./media.js";import{loginView as n}from"./login.js";import{usersView as f}from"./users.js";import{userEditorView as w}from"./user-editor.js";import{pluginsView as c}from"./plugins.js";import{documentationView as V}from"./documentation.js";import{tutorialsView as a}from"./tutorials.js";import{apiReferenceView as l}from"./api-reference.js";import{collectionsView as d}from"./collections.js";import{collectionEditorView as u}from"./collection-editor.js";import{collectionEntriesView as E}from"./collection-entries.js";import{formsView as g}from"./forms.js";import{formEditorView as v}from"./form-editor.js";import{formSubmissionsView as b}from"./form-submissions.js";import{viewsListView as j}from"./views-list.js";import{viewEditorView as L}from"./view-editor.js";import{viewPreviewView as y}from"./view-preview.js";import{actionsListView as h}from"./actions-list.js";import{actionEditorView as k}from"./action-editor.js";import{proDocsView as D}from"./pro-docs.js";import{blocksView as P}from"./blocks.js";import{blockEditorView as R}from"./block-editor.js";import"./block-editor-enhance.js";import{componentsView as S}from"./components.js";import{componentEditorView as x}from"./component-editor.js";import{myProfileView as M}from"./my-profile.js";import{rolesView as U}from"./roles.js";import{roleEditorView as q}from"./role-editor.js";import{effectsView as z}from"./effects.js";import{menusView as A}from"./menus.js";import{menuEditorView as B}from"./menu-editor.js";import{menuLocationsView as C}from"./menu-locations.js";import{projectsView as F}from"./projects.js";import{projectEditorView as G}from"./project-editor.js";import{projectDetailView as H}from"./project-detail.js";import{projectSettingsView as I}from"./project-settings.js";const J={templateUrl:"",async onMount(){location.hash="#/menus"}};export const views={projects:F,projectEditor:G,projectDetail:H,projectSettings:I,dashboard:o,pages:i,pageEditor:r,settings:t,navigation:e,layouts:s,media:p,login:n,users:f,userEditor:w,plugins:c,documentation:V,tutorials:a,apiReference:l,collections:d,collectionEditor:u,collectionEntries:E,forms:g,formEditor:v,formSubmissions:b,viewsList:j,viewEditor:L,viewPreview:y,actionsList:h,actionEditor:k,proDocs:D,blocks:P,blockEditor:R,components:S,componentEditor:x,myProfile:M,roles:U,roleEditor:q,effects:z,notifications:m,menus:A,menuEditor:B,menuLocations:C,menusRedirect:J};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import{api as v}from"../api.js";import{openIconPicker as M}from"../lib/card-builder.js";let R=1;const k=()=>"i_"+R++;function I(t,l=null,d=[]){for(const i of t||[]){const e=k();d.push({id:e,parentId:l,text:i.text||"",url:i.url||"",icon:i.icon||"",visibility:i.visibility||"",permission:i.permission||"",hidden:!!i.hidden,bundled:!!i.bundled}),Array.isArray(i.items)&&i.items.length&&I(i.items,e,d)}return d}function W(t){const l=new Map;for(const i of t)l.has(i.parentId)||l.set(i.parentId,[]),l.get(i.parentId).push(i);function d(i){return(l.get(i)||[]).map(e=>{const r=d(e.id);return{text:(e.text||"").trim(),url:(e.url||"").trim(),...e.icon&&{icon:e.icon.trim()},...e.visibility&&{visibility:e.visibility},...e.permission&&{permission:e.permission},...e.hidden&&{hidden:!0},...e.bundled&&{bundled:!0},...r.length&&{items:r}}})}return d(null)}function y(t){return String(t||"").replace(/&/g,"&").replace(/"/g,""").replace(/</g,"<")}function D(t,l){let d=0,i=l.find(e=>e.id===t);for(;i&&i.parentId;)d++,i=l.find(e=>e.id===i.parentId);return d}function b(t,l,d=()=>""){t.html("");function i(e){for(const r of l.filter(w=>w.parentId===e)){const w=D(r.id,l);t.append(`
|
|
2
|
+
<div class="menu-tree-row${r.hidden?" menu-tree-row--hidden":""}" data-id="${r.id}" style="margin-left:${w*28}px">
|
|
3
|
+
<span class="menu-tree-grip" data-icon="grip-vertical"></span>
|
|
4
|
+
<input type="text" class="form-input me-text" value="${y(r.text)}" placeholder="Label">
|
|
5
|
+
<input type="text" class="form-input me-url" value="${y(r.url)}" placeholder="/url">
|
|
6
|
+
<div class="me-icon-wrap">
|
|
7
|
+
<input type="text" class="form-input me-icon" value="${y(r.icon)}" placeholder="icon">
|
|
8
|
+
<button class="btn btn-sm btn-ghost me-icon-pick" title="Browse icons" type="button">\u229E</button>
|
|
9
|
+
</div>
|
|
10
|
+
<input type="text" class="form-input me-vis" value="${y(r.visibility)}" placeholder="visibility">
|
|
11
|
+
<select class="form-select me-perm" data-id="${r.id}">${d(r.permission)}</select>
|
|
12
|
+
<button class="btn btn-sm btn-ghost me-outdent" data-tooltip="Outdent"><span data-icon="chevron-left"></span></button>
|
|
13
|
+
<button class="btn btn-sm btn-ghost me-indent" data-tooltip="Indent"><span data-icon="chevron-right"></span></button>
|
|
14
|
+
<button class="btn btn-sm btn-ghost me-up" data-tooltip="Move up"><span data-icon="arrow-up"></span></button>
|
|
15
|
+
<button class="btn btn-sm btn-ghost me-down" data-tooltip="Move down"><span data-icon="arrow-down"></span></button>
|
|
16
|
+
<button class="btn btn-sm btn-ghost me-hidden ${r.hidden?"active":""}" data-tooltip="${r.hidden?"Show":"Hide"}"><span data-icon="eye-off"></span></button>
|
|
17
|
+
<button class="btn btn-sm btn-danger me-remove" data-tooltip="Remove"><span data-icon="trash"></span></button>
|
|
18
|
+
</div>
|
|
19
|
+
`),i(r.id)}}i(null),Domma.icons.scan(t.get(0))}function g(t,l){t.find(".menu-tree-row").each(function(){const d=$(this).data("id"),i=l.find(e=>e.id===d);i&&(i.text=$(this).find(".me-text").val(),i.url=$(this).find(".me-url").val(),i.icon=$(this).find(".me-icon").val(),i.visibility=$(this).find(".me-vis").val(),i.permission=$(this).find(".me-perm").val())})}function L(t,l){const d={};for(const i of l){const e=t.querySelector(`[name="${i}"]`);e&&(d[i]=e.type==="checkbox"?e.checked:e.value)}return d}export const menuEditorView={templateUrl:"/admin/js/templates/menu-editor.html",async onMount(t){const l=location.hash.match(/^#\/menus\/edit\/(.+)$/),d=l?decodeURIComponent(l[1]):null,i=!d;let e;if(i)e={slug:"",name:"",description:"",items:[]};else try{e=await v.menus.get(d)}catch(s){E.toast(`Failed to load menu: ${s.message||s}`,{type:"error"}),location.hash="#/menus";return}const r=await v.projects.list().catch(()=>[]),w=await H.get("/api/auth/permissions-registry").catch(()=>({resources:[]})),S='<option value="">\u2014 no permission gate \u2014</option>',f=s=>S+(w.resources||[]).map(o=>{const a=(s||"")===o.key?" selected":"";return`<option value="${o.key}"${a}>${o.label||o.key}</option>`}).join("");t.find("#me-title").text(i?"New menu":`Edit "${e.slug}"`);let n=I(e.items);const u=t.find("#me-items-list");b(u,n,f),t.find("#me-add-item").on("click",()=>{g(u,n),n.push({id:k(),parentId:null,text:"",url:"/",icon:"",visibility:"",permission:"",hidden:!1,bundled:!1}),b(u,n,f)}),t.off("click",".me-remove").on("click",".me-remove",function(){const s=$(this).closest(".menu-tree-row").data("id");n=n.filter(o=>o.id!==s&&o.parentId!==s),b(u,n,f)}),t.off("click",".me-indent").on("click",".me-indent",function(){g(u,n);const s=$(this).closest(".menu-tree-row").data("id"),o=n.find(c=>c.id===s);if(!o)return;const a=n.filter(c=>c.parentId===o.parentId),p=a.findIndex(c=>c.id===s);p<=0||(o.parentId=a[p-1].id,b(u,n,f))}),t.off("click",".me-outdent").on("click",".me-outdent",function(){g(u,n);const s=$(this).closest(".menu-tree-row").data("id"),o=n.find(p=>p.id===s);if(!o||o.parentId==null)return;const a=n.find(p=>p.id===o.parentId);o.parentId=a?a.parentId:null,b(u,n,f)}),t.off("click",".me-up").on("click",".me-up",function(){g(u,n);const s=$(this).closest(".menu-tree-row").data("id"),o=n.filter(m=>m.parentId===n.find(h=>h.id===s)?.parentId),a=o.findIndex(m=>m.id===s);if(a<=0)return;const p=n.indexOf(o[a]),c=n.indexOf(o[a-1]);[n[p],n[c]]=[n[c],n[p]],b(u,n,f)}),t.off("click",".me-down").on("click",".me-down",function(){g(u,n);const s=$(this).closest(".menu-tree-row").data("id"),o=n.filter(m=>m.parentId===n.find(h=>h.id===s)?.parentId),a=o.findIndex(m=>m.id===s);if(a===-1||a>=o.length-1)return;const p=n.indexOf(o[a]),c=n.indexOf(o[a+1]);[n[p],n[c]]=[n[c],n[p]],b(u,n,f)}),t.off("click",".me-hidden").on("click",".me-hidden",function(){g(u,n);const s=$(this).closest(".menu-tree-row").data("id"),o=n.find(a=>a.id===s);o&&(o.hidden=!o.hidden),b(u,n,f)}),E.tabs(t.find("#me-tabs").get(0)),t.find("#me-variant").val(e.variant||""),t.find("#me-position").val(e.position||""),t.find("#me-fontFamily").val(e.style?.fontFamily||""),t.find("#me-fontSize").val(e.style?.fontSize||""),t.find("#me-fontWeight").val(e.style?.fontWeight||""),t.find("#me-letterSpacing").val(e.style?.letterSpacing||""),t.find("#me-iconSize").val(e.style?.iconSize||"");const{getProjectFromHash:j}=await import("../lib/project-context.js"),F=i?j():null,x=t.find("#me-project");x.html('<option value="">\u2014 none \u2014</option>'+r.map(s=>`<option value="${y(s.slug)}">${y(s.name||s.slug)}</option>`).join("")),t.find("#me-slug").val(e.slug||""),t.find("#me-name").val(e.name||""),t.find("#me-description").val(e.description||""),x.val(e.meta?.project||F||""),Domma.icons.scan(t.get(0)),t.off("click",".me-icon-pick").on("click",".me-icon-pick",function(s){s.preventDefault();const o=$(this).siblings(".me-icon").get(0);o&&M(a=>{o.value=a,o.dispatchEvent(new Event("input",{bubbles:!0}))})}),t.find("#me-save").on("click",async()=>{g(u,n);const s={variant:t.find("#me-variant").val(),position:t.find("#me-position").val(),fontFamily:t.find("#me-fontFamily").val(),fontSize:t.find("#me-fontSize").val(),fontWeight:t.find("#me-fontWeight").val(),letterSpacing:t.find("#me-letterSpacing").val(),iconSize:t.find("#me-iconSize").val()},o={slug:t.find("#me-slug").val().trim(),name:t.find("#me-name").val().trim(),description:t.find("#me-description").val().trim(),project:t.find("#me-project").val()},a=["fontFamily","fontSize","fontWeight","letterSpacing","iconSize"],p=Object.fromEntries(Object.entries(s).filter(([m,h])=>a.includes(m)&&h)),c={slug:o.slug,name:o.name,description:o.description,...s.variant&&{variant:s.variant},...s.position&&{position:s.position},...Object.keys(p).length&&{style:p},items:W(n),meta:{...e.meta||{},project:o.project||null}};try{if(i)await v.menus.create(c);else if(c.slug!==e.slug){await v.menus.create(c);const m=await v.menuLocations.get();let h=!1;for(const[O,z]of Object.entries(m))z===e.slug&&(m[O]=c.slug,h=!0);h&&await v.menuLocations.save(m),await v.menus.remove(e.slug)}else await v.menus.update(e.slug,c);E.toast("Saved.",{type:"success"}),location.hash="#/menus/edit/"+encodeURIComponent(c.slug)}catch(m){E.toast(`Save failed: ${m.message||m}`,{type:"error"})}})}};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{api as i}from"../api.js";function m(s){if(!Array.isArray(s)||s.length===0)return 0;let l=0;for(const c of s){const r=Array.isArray(c.items)&&c.items.length?m(c.items):0;r>l&&(l=r)}return l+1}function a(s){return String(s??"").replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""").replace(/'/g,"'")}export const menuLocationsView={templateUrl:"/admin/js/templates/menu-locations.html",async onMount(s){const[l,c,r]=await Promise.all([i.menuLocations.registry().catch(()=>[]),i.menuLocations.get().catch(()=>({})),i.menus.list().catch(()=>[])]),u=await Promise.all(r.map(e=>i.menus.get(e.slug).catch(()=>null))),p={};for(const e of u)e&&e.slug&&(p[e.slug]=m(e.items||[]));const d=(l||[]).map(e=>({slot:e.slot,label:e.label,source:e.source,description:e.description,maxDepth:e.maxDepth,menu:c[e.slot]||""}));T.create("#ml-table",{data:d,emptyMessage:"No menu slots registered.",columns:[{key:"slot",title:"Slot",render:e=>`<code>${a(e)}</code>`},{key:"label",title:"Label",render:e=>a(e)},{key:"source",title:"Source",render:e=>e?`<span class="badge badge-secondary">${a(e)}</span>`:""},{key:"description",title:"Description",render:e=>e?`<span class="text-muted">${a(e)}</span>`:""},{key:"menu",title:"Mapped menu",render:(e,t)=>{const n=['<option value="">— none —</option>'].concat(r.map(o=>{const g=o.slug===t.menu?" selected":"",h=a(o.name||o.slug);return`<option value="${a(o.slug)}"${g}>${h}</option>`}));return`<select class="form-select ml-select" data-slot="${a(t.slot)}">${n.join("")}</select>`}},{key:"_warning",title:"",render:(e,t)=>{if(!t.menu||t.maxDepth==null)return"";const n=p[t.menu]||0;if(n>t.maxDepth){const o=`This slot supports max depth ${t.maxDepth}; menu is depth ${n}`;return`<span class="badge badge-warning" data-tooltip="${a(o)}">⚠ depth</span>`}return""}}]}),Domma.icons.scan("#ml-table"),document.querySelectorAll("#ml-table [data-tooltip]").forEach(e=>{E.tooltip(e,{content:e.getAttribute("data-tooltip"),position:"top"})}),s.find("#ml-save").on("click",async()=>{const e={};s.find(".ml-select").each(function(){const t=$(this).data("slot"),n=$(this).val();t&&n&&(e[t]=n)});try{await i.menuLocations.save(e),E.toast("Locations saved.",{type:"success"})}catch(t){E.toast(`Save failed: ${t.message||t}`,{type:"error"})}})}};
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import{api as o}from"../api.js";import{filterByProject as g,getProjectFromHash as b}from"../lib/project-context.js";function l(s){return String(s).replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""").replace(/'/g,"'")}export const menusView={templateUrl:"/admin/js/templates/menus.html",async onMount(s){const[i,d]=await Promise.all([o.menus.list().catch(()=>[]),o.menuLocations.get().catch(()=>({}))]),r=b(),p=r?g(i,r):i,a={};for(const[e,t]of Object.entries(d||{}))t&&(a[t]||(a[t]=[]),a[t].push(e));T.create("#menus-table",{data:p,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 n=a[t.slug]||[];return n.length?n.map(c=>`<span class="badge badge-info">${l(c)}</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 n=(a[t.slug]||[]).length>0,c=n?'data-tooltip="Unmap this menu before deleting"':"",m=n?"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"></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}" ${m} ${c}><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"})}),s.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"})}}),s.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(n){E.toast(`Delete failed: ${n.message||n}`,{type:"error"})}})}};
|