domma-cms 0.44.0 → 0.45.1
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 +53 -53
- package/README.md +27 -27
- package/admin/css/admin.css +1 -1
- package/admin/dist/domma/domma-tools.css +3 -3
- package/admin/dist/domma/domma-tools.min.js +3 -3
- package/admin/index.html +2 -2
- package/admin/js/app.js +1 -1
- package/admin/js/lib/card-builder.js +2 -2
- package/admin/js/lib/crud-tutorial.js +1 -1
- package/admin/js/lib/effect-defs.js +1 -1
- package/admin/js/lib/effects-builder.js +1 -1
- package/admin/js/lib/image-editor.js +1 -1
- package/admin/js/lib/markdown-toolbar.js +2 -2
- package/admin/js/lib/project-context.js +1 -1
- package/admin/js/lib/project-quick-create.js +1 -1
- package/admin/js/lib/scribe-composer.js +1 -1
- package/admin/js/lib/server-restart.js +1 -1
- package/admin/js/lib/shortcode-context-menu.js +2 -2
- package/admin/js/lib/simple-editor.js +1 -1
- package/admin/js/lib/themes.js +1 -1
- package/admin/js/lib/timeline-builder.js +1 -1
- package/admin/js/templates/action-editor.html +8 -8
- package/admin/js/templates/api-endpoint-editor.html +7 -7
- package/admin/js/templates/api-endpoints.html +1 -1
- package/admin/js/templates/api-reference.html +1 -1
- package/admin/js/templates/block-editor.html +6 -6
- package/admin/js/templates/collection-editor.html +1 -1
- package/admin/js/templates/component-editor.html +2 -2
- package/admin/js/templates/dashboard/cache.html +3 -3
- package/admin/js/templates/dashboard/journeys.html +3 -3
- package/admin/js/templates/dashboard/kpi-strip.html +3 -3
- package/admin/js/templates/dashboard/top-pages.html +1 -1
- package/admin/js/templates/dashboard/traffic-chart.html +1 -1
- package/admin/js/templates/docs/api-actions.html +1 -1
- package/admin/js/templates/docs/api-collections.html +4 -4
- package/admin/js/templates/docs/api-pages.html +2 -2
- package/admin/js/templates/docs/api-plugins.html +1 -1
- package/admin/js/templates/docs/api-settings.html +2 -2
- package/admin/js/templates/docs/api-users.html +1 -1
- package/admin/js/templates/docs/components-howto.html +4 -4
- package/admin/js/templates/docs/components-reference.html +21 -21
- package/admin/js/templates/docs/components-rules.html +14 -14
- package/admin/js/templates/docs/components-walkthrough.html +13 -13
- package/admin/js/templates/docs/tutorial-crud.html +49 -49
- package/admin/js/templates/docs/tutorial-forms.html +4 -4
- package/admin/js/templates/docs/tutorial-plugin.html +10 -10
- package/admin/js/templates/docs/usage-actions.html +6 -6
- package/admin/js/templates/docs/usage-cta-shortcode.html +9 -9
- package/admin/js/templates/docs/usage-dconfig.html +4 -4
- package/admin/js/templates/docs/usage-navigation.html +1 -1
- package/admin/js/templates/docs/usage-pages.html +1 -1
- package/admin/js/templates/docs/usage-shortcodes.html +24 -24
- package/admin/js/templates/docs/usage-users-roles.html +3 -3
- package/admin/js/templates/docs/usage-views.html +4 -4
- package/admin/js/templates/documentation.html +1 -1
- package/admin/js/templates/effects.html +752 -752
- package/admin/js/templates/form-editor.html +5 -5
- package/admin/js/templates/forms.html +17 -17
- package/admin/js/templates/login.html +5 -5
- package/admin/js/templates/menu-editor.html +10 -10
- package/admin/js/templates/my-profile.html +17 -17
- package/admin/js/templates/navigation.html +11 -11
- package/admin/js/templates/notifications.html +1 -1
- package/admin/js/templates/page-editor.html +4 -4
- package/admin/js/templates/pro-docs.html +14 -14
- package/admin/js/templates/role-editor.html +70 -70
- package/admin/js/templates/roles.html +10 -10
- package/admin/js/templates/settings.html +10 -10
- package/admin/js/templates/tutorials.html +3 -3
- package/admin/js/templates/view-editor.html +15 -15
- package/admin/js/views/action-editor.js +1 -1
- package/admin/js/views/actions-list.js +1 -1
- package/admin/js/views/api-endpoint-editor.js +2 -2
- package/admin/js/views/api-endpoints.js +2 -2
- package/admin/js/views/api-tokens.js +3 -3
- package/admin/js/views/blocks.js +2 -2
- package/admin/js/views/collection-editor.js +1 -1
- package/admin/js/views/collection-entries.js +1 -1
- package/admin/js/views/collections.js +1 -1
- package/admin/js/views/component-editor.js +1 -1
- package/admin/js/views/components.js +4 -4
- package/admin/js/views/dashboard/widgets/cache.js +1 -1
- package/admin/js/views/dashboard/widgets/journeys.js +1 -1
- package/admin/js/views/dashboard/widgets/kpi-strip.js +1 -1
- package/admin/js/views/form-editor.js +7 -7
- package/admin/js/views/form-submissions.js +1 -1
- package/admin/js/views/forms.js +1 -1
- package/admin/js/views/layouts.js +1 -1
- package/admin/js/views/login.js +2 -2
- package/admin/js/views/menu-editor.js +3 -3
- package/admin/js/views/menu-locations.js +1 -1
- package/admin/js/views/menus.js +3 -3
- package/admin/js/views/navigation.js +8 -8
- package/admin/js/views/page-editor.js +40 -40
- package/admin/js/views/pages.js +3 -3
- package/admin/js/views/plugin-code.js +2 -2
- package/admin/js/views/plugins.js +1 -1
- package/admin/js/views/project-settings.js +1 -1
- package/admin/js/views/projects.js +1 -1
- package/admin/js/views/roles.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/view-preview.js +1 -1
- package/admin/js/views/views-list.js +1 -1
- package/bin/cli.js +8 -8
- package/bin/lib/config-merge.js +44 -44
- package/bin/update.js +16 -16
- package/config/plugins.json +1 -1
- package/package.json +2 -2
- package/plugins/_template/admin/views/index.js +1 -1
- package/plugins/_template/config.js +1 -1
- package/plugins/_template/plugin.js +1 -1
- package/plugins/analytics/admin/templates/analytics.html +3 -3
- package/plugins/analytics/admin/views/analytics.js +1 -1
- package/plugins/analytics/config.js +1 -1
- package/plugins/analytics/plugin.js +4 -4
- package/plugins/analytics/plugin.json +1 -1
- package/plugins/analytics/public/inject-body.html +2 -2
- package/plugins/analytics/public/inject-head.html +1 -1
- package/plugins/blog/admin/templates/blog.html +4 -4
- package/plugins/blog/admin/views/blog.js +3 -3
- package/plugins/blog/admin/views/categories.js +2 -2
- package/plugins/blog/admin/views/comments.js +4 -4
- package/plugins/blog/admin/views/post-editor.js +1 -1
- package/plugins/blog/plugin.js +2 -2
- package/plugins/blog/plugin.json +4 -4
- package/plugins/contacts/admin/views/contacts.js +8 -8
- package/plugins/contacts/plugin.js +4 -4
- package/plugins/contacts/plugin.json +1 -1
- package/plugins/invoice/admin/templates/editor.html +6 -6
- package/plugins/invoice/admin/templates/index.html +4 -4
- package/plugins/invoice/admin/views/editor.js +3 -3
- package/plugins/invoice/admin/views/index.js +4 -4
- package/plugins/invoice/admin/views/issuers.js +1 -1
- package/plugins/invoice/admin/views/party-view.js +3 -3
- package/plugins/invoice/admin/views/receivers.js +1 -1
- package/plugins/invoice/config.js +1 -1
- package/plugins/invoice/plugin.js +1 -1
- package/plugins/invoice/plugin.json +4 -4
- package/plugins/notes/admin/views/notes.js +4 -4
- package/plugins/notes/plugin.js +1 -1
- package/plugins/notes/plugin.json +1 -1
- package/plugins/site-search/admin/templates/site-search.html +3 -3
- package/plugins/site-search/admin/views/site-search.js +1 -1
- package/plugins/site-search/config.js +1 -1
- package/plugins/site-search/plugin.js +4 -4
- package/plugins/site-search/plugin.json +1 -1
- package/plugins/surveys/admin/templates/results.html +1 -1
- package/plugins/surveys/admin/templates/survey-editor.html +4 -4
- package/plugins/surveys/admin/views/audience.js +6 -6
- package/plugins/surveys/admin/views/results.js +3 -3
- package/plugins/surveys/admin/views/survey-editor.js +1 -1
- package/plugins/surveys/admin/views/surveys.js +2 -2
- package/plugins/surveys/plugin.js +1 -1
- package/plugins/surveys/plugin.json +4 -4
- package/plugins/theme-switcher/admin/views/theme-switcher.js +1 -1
- package/plugins/theme-switcher/plugin.json +3 -3
- package/plugins/theme-switcher/public/inject-body.html +28 -28
- package/plugins/theme-switcher/public/inject-head.html +1 -1
- package/plugins/todo/plugin.js +1 -1
- package/public/css/forms.css +1 -1
- package/public/js/collection-browser.js +1 -1
- package/public/js/collection-context.js +1 -1
- package/public/js/form-logic-engine.js +1 -1
- package/public/js/forms.js +2 -1
- package/public/js/site.js +1 -1
- package/scripts/build.js +14 -14
- package/scripts/create-plugin.js +2 -2
- package/scripts/fresh.js +2 -2
- package/scripts/gen-instance-secret.js +1 -1
- package/scripts/pro.js +12 -12
- package/scripts/reset.js +2 -2
- package/scripts/setup.js +15 -15
- package/scripts/users.js +5 -5
- package/scripts/verify-assets.mjs +6 -6
- package/server/config.js +1 -1
- package/server/middleware/auth.js +253 -253
- package/server/middleware/managerAuth.js +3 -3
- package/server/routes/api/actions.js +4 -4
- package/server/routes/api/api-endpoints.js +7 -7
- package/server/routes/api/api-tokens.js +5 -5
- package/server/routes/api/auth.js +309 -309
- package/server/routes/api/blocks.js +3 -3
- package/server/routes/api/collections.js +18 -18
- package/server/routes/api/dashboard.js +5 -5
- package/server/routes/api/effects.js +2 -2
- package/server/routes/api/endpoints-public.js +5 -5
- package/server/routes/api/forms.js +47 -47
- package/server/routes/api/menu-locations.js +3 -3
- package/server/routes/api/menus.js +7 -7
- package/server/routes/api/navigation.js +42 -42
- package/server/routes/api/notifications.js +2 -2
- package/server/routes/api/pages.js +14 -2
- package/server/routes/api/plugin-marketplace.js +3 -3
- package/server/routes/api/plugins.js +6 -6
- package/server/routes/api/projects.js +9 -9
- package/server/routes/api/scaffold.js +7 -7
- package/server/routes/api/settings.js +6 -6
- package/server/routes/api/sidebar.js +1 -1
- package/server/routes/api/users.js +5 -5
- package/server/routes/api/versions.js +1 -1
- package/server/routes/api/views.js +1 -1
- package/server/routes/docs-public.js +3 -3
- package/server/routes/public.js +14 -14
- package/server/server.js +34 -16
- package/server/services/actions.js +19 -19
- package/server/services/adapters/FileAdapter.js +2 -2
- package/server/services/adapters/MongoAdapter.js +5 -5
- package/server/services/apiEndpoints.js +15 -15
- package/server/services/apiTokens.js +7 -7
- package/server/services/blocks.js +13 -13
- package/server/services/cache/drivers/MemoryDriver.js +3 -3
- package/server/services/cache/drivers/NoneDriver.js +1 -1
- package/server/services/cache/index.js +5 -5
- package/server/services/collections.js +14 -14
- package/server/services/components.js +9 -9
- package/server/services/connectionManager.js +4 -4
- package/server/services/content.js +25 -2
- package/server/services/docs.js +14 -14
- package/server/services/email.js +167 -167
- package/server/services/filterEngine.js +6 -6
- package/server/services/forms.js +18 -10
- package/server/services/health.js +1 -1
- package/server/services/hooks.js +7 -7
- package/server/services/images.js +4 -4
- package/server/services/managerClient.js +1 -1
- package/server/services/markdown.js +79 -79
- package/server/services/menuRender.js +5 -5
- package/server/services/menus-migration.js +3 -3
- package/server/services/menus.js +31 -31
- package/server/services/permissionRegistry.js +1 -1
- package/server/services/pluginFiles.js +5 -5
- package/server/services/pluginInstaller.js +17 -17
- package/server/services/pluginScaffold.js +1 -1
- package/server/services/plugins.js +17 -17
- package/server/services/presetCollections.js +2 -2
- package/server/services/projects.js +35 -26
- package/server/services/recipes/contact-list.json +1 -1
- package/server/services/recipes/onboarding.json +8 -8
- package/server/services/references.js +8 -8
- package/server/services/renderer.js +9 -9
- package/server/services/roles.js +9 -9
- package/server/services/rowAccess.js +10 -10
- package/server/services/scaffolder.js +30 -30
- package/server/services/sidebar-migration.js +10 -10
- package/server/services/sitemap.js +1 -1
- package/server/services/userProfiles.js +199 -199
- package/server/services/userRoles.js +7 -7
- package/server/services/users.js +302 -302
- package/server/services/versions.js +3 -3
- package/server/services/viewPipeline.js +6 -6
- package/server/services/views.js +11 -11
- package/server/templates/page.html +5 -5
|
@@ -25,8 +25,8 @@ import{api as S}from"../api.js";import{colourToCss as rt}from"/public/js/menu-de
|
|
|
25
25
|
<button class="btn btn-sm btn-secondary me-edit" data-tooltip="Edit"><span data-icon="edit"></span></button>
|
|
26
26
|
<button class="btn btn-sm btn-ghost me-remove" data-tooltip="Remove"><span data-icon="trash"></span></button>
|
|
27
27
|
</div>
|
|
28
|
-
`),i(o.id)}}i(null),Domma.icons.scan(t.get(0)),t.get(0).querySelectorAll("[data-tooltip]").forEach(n=>{E.tooltip(n,{content:n.getAttribute("data-tooltip"),position:"top"})})}function F(t,l){t.find(".menu-tree-row").each(function(){const a=$(this).data("id"),i=l.find(k=>k.id===a);if(!i||i.type==="separator")return;const n=$(this).find(".me-text");n.length&&(i.text=n.val());const o=$(this).find(".me-url");o.length&&(i.url=o.val());const c=$(this).find(".me-icon");c.length&&(i.icon=c.val());const h=$(this).find(".me-vis");h.length&&(i.visibility=h.val());const x=$(this).find(".me-perm");x.length&&(i.permission=x.val())})}function _t(t,l){const a={};for(const i of l){const n=t.querySelector(`[name="${i}"]`);n&&(a[i]=n.type==="checkbox"?n.checked:n.value)}return a}function g(t,l={},...a){const i=document.createElement(t);Object.assign(i,l);for(const n of a)n!=null&&i.append(n);return i}function j(t,l,a){const i=g("label",{className:"form-field"},g("span",{className:"form-label",textContent:t}),l);return a&&i.append(g("small",{className:"form-hint",textContent:a,style:"display:block;font-size:.78em;opacity:.7;margin-top:.25rem;line-height:1.35"})),i}function ht(t,l){const a=[["","\u2014 Everyone \u2014"],["public","Public \u2014 anyone, incl. logged-out"],...(l||[]).map(n=>[n.name,`${n.label||n.name} and above`]),["private","Private \u2014 super-admin only"]];t&&!a.some(([n])=>n===t)&&a.push([t,`${t} (custom)`]);const i=B(a,t||"");return i.name="visibility",i}function B(t,l){const a=g("select",{className:"form-select"});return t.forEach(([i,n])=>a.add(new Option(n,i))),a.value=l??"",a}function K(t,l){const a=typeof l=="string"&&l.startsWith("#"),i=B([["","\u2014 none \u2014"],["primary","primary"],["success","success"],["danger","danger"],["warning","warning"],["info","info"],["neutral","neutral"],["__custom__","Custom\u2026"]],a?"__custom__":l||""),n=g("input",{type:"color",className:"form-input me-colour-hex",value:a?l:"#000000"});n.style.display=a?"":"none",i.addEventListener("change",()=>{n.style.display=i.value==="__custom__"?"":"none"});const o=j(t,i);return o.append(n),o._read=()=>i.value==="__custom__"?n.value:i.value,o}function yt(t,l){const a=g("input",{className:"form-input me-icon",name:"icon",value:l||""}),i=g("span",{className:"me-icon-preview"});l&&i.setAttribute("data-icon",l);const n=g("button",{type:"button",className:"btn btn-sm btn-ghost",textContent:"Browse\u2026"}),o=c=>{c?i.setAttribute("data-icon",c):i.removeAttribute("data-icon"),Domma.icons.scan(i.parentNode||i)};return n.addEventListener("click",()=>ct(c=>{a.value=c,o(c)})),a.addEventListener("input",()=>o(a.value.trim())),j(t,g("div",{className:"me-icon-field"},a,n,i))}function Z(...t){return g("div",{className:"me-field-grid"},...t)}async function wt(t,l,a,i,n){const o=f=>g("h4",{className:"me-form-section",textContent:f}),c=j("Label",g("input",{className:"form-input",name:"text",value:t.text||""}),"The text shown for this item."),h=j("URL",g("input",{className:"form-input",name:"url",value:t.url||""}),"Where it links. Must start with /, #, https://, or mailto: . Leave blank for a parent that only groups sub-items."),x=yt("Icon",t.icon||""),k=j("Visibility",ht(t.visibility||"",i),"Who can see this item, by role seniority. Picking a role shows it to that role and anyone more senior. Blank = everyone."),D=B([["","\u2014 no permission gate \u2014"],...(l||[]).map(f=>[f.key,f.label||f.key])],t.permission||"");D.name="permission";const Y=j("Permission",D,"Hide unless the viewer holds this specific permission. Independent of Visibility \u2014 both must pass."),J=j("Badge text",g("input",{className:"form-input",name:"badgeText",value:t.badge?.text||""})),X=B([["","\u2014 none \u2014"],...(a||[]).map(f=>[f.slug,f.title||f.slug])],t.badge?.countFrom||"");X.name="badgeCount";const V=j("Badge count from",X,"Show a live count of this collection\u2019s entries as the badge."),U=K("Badge colour",t.badge?.variant||""),d=B([["link","Link"],["pill","Pill"]],t.pill?"pill":"link");d.name="renderAs";const w=j("Render as",d,"Pill draws the item as a rounded chip. Pill styling applies to top-level navbar items."),v=j("Pill style",B([["filled","Filled"],["outline","Outline"]],t.pill?.style||"filled"));v.querySelector("select").name="pillStyle";const z=K("Pill colour",t.pill?.variant||""),C=g("div",{className:"me-pill-wrap"},Z(v,z));C.style.display=t.pill?"":"none",d.addEventListener("change",()=>{C.style.display=d.value==="pill"?"":"none"});const R=K("Text colour",t.colour||""),W=g("button",{className:"btn btn-primary",textContent:"Apply"}),A=g("div",{className:"me-item-form"},o("Link"),c,h,x,o("Access"),Z(k,Y),o("Badge"),Z(J,U),V,o("Appearance"),w,C,R,W),_=E.slideover({title:"Edit menu item",size:"lg",position:"right"});_.element.appendChild(A),_.open();const T=_.element.closest(".dm-slideover")||_.element.querySelector(".dm-slideover")||_.element;T.style.setProperty("width",ut+"px","important"),T.style.setProperty("max-width","95vw","important"),Domma.icons.scan(A);const I=f=>A.querySelector(`[name="${f}"]`)?.value||"";W.addEventListener("click",()=>{t.text=I("text"),t.url=I("url"),t.icon=I("icon"),t.visibility=I("visibility"),t.permission=I("permission");const f=I("badgeText"),L=I("badgeCount"),N=U._read();if(t.badge=f||L?{...f&&{text:f},...L&&{countFrom:L},...N&&{variant:N}}:null,I("renderAs")==="pill"){const P=z._read();t.pill={style:I("pillStyle")||"filled",...P&&{variant:P}}}else t.pill=null;t.colour=R._read(),_.close(),n()})}export const menuEditorView={templateUrl:"/admin/js/templates/menu-editor.html",async onMount(t){const l=location.hash.match(/^#\/menus\/edit\/(.+)$/),a=l?decodeURIComponent(l[1]):null,i=!a,n=E.loader(t.get(0),{type:"dots"});let o,c,h,x,k,D,Y;try{if(i)o={slug:"",name:"",description:"",items:[]};else try{o=await S.menus.get(a)}catch(e){E.toast(`Failed to load menu: ${e.message||e}`,{type:"error"}),location.hash="#/menus";return}c=await S.projects.list().catch(()=>[]),h=await H.get("/api/auth/permissions-registry").catch(()=>({resources:[]})),x=await S.collections.list().catch(()=>[]),k=await S.get("/collections/roles/entries?limit=100").catch(()=>null),D=await S.menuLocations.registry().catch(()=>[]),Y=await S.pages.list().catch(()=>[])}finally{n.destroy()}const X=(Array.isArray(k)?k:k?.entries||[]).map(e=>({name:e.data?.name,label:e.data?.label,level:e.data?.level??99})).filter(e=>e.name).sort((e,s)=>s.level-e.level),V='<option value="">\u2014 no permission gate \u2014</option>',U=e=>V+(h.resources||[]).map(s=>{const r=(e||"")===s.key?" selected":"";return`<option value="${s.key}"${r}>${s.label||s.key}</option>`}).join("");t.find("#me-title").text(i?"New menu":`Edit "${o.slug}"`);let d=et(o.items);const w=t.find("#me-items-list");function v(){vt(w,d,U),gt(w,()=>d,v)}v(),t.find("#me-add-item").on("click",()=>{F(w,d),d.push({id:q(),parentId:null,text:"",url:"/",icon:"",visibility:"",permission:"",hidden:!1,bundled:!1}),v()}),t.find("#me-add-separator").on("click",()=>{F(w,d),d.push({id:q(),parentId:null,type:"separator"}),v()});function z(e){return function(){F(w,d);const s=$(this).closest(".menu-tree-row").data("id"),r=d.findIndex(p=>p.id===s);if(r===-1)return;const m=d[r],u=e==="separator"?{id:q(),parentId:m.parentId,type:"separator"}:{id:q(),parentId:m.parentId,text:"",url:"/",icon:"",visibility:"",permission:"",hidden:!1,bundled:!1};d.splice(r+1,0,u),v()}}t.off("click",".me-ins-item").on("click",".me-ins-item",z("item")),t.off("click",".me-ins-sep").on("click",".me-ins-sep",z("separator")),t.off("click",".me-remove").on("click",".me-remove",function(){const e=$(this).closest(".menu-tree-row").data("id");d=d.filter(s=>s.id!==e&&s.parentId!==e),v()}),t.off("click",".me-indent").on("click",".me-indent",function(){F(w,d);const e=$(this).closest(".menu-tree-row").data("id"),s=d.find(u=>u.id===e);if(!s)return;const r=d.filter(u=>u.parentId===s.parentId),m=r.findIndex(u=>u.id===e);m<=0||r[m-1].type!=="separator"&&(s.parentId=r[m-1].id,v())}),t.off("click",".me-outdent").on("click",".me-outdent",function(){F(w,d);const e=$(this).closest(".menu-tree-row").data("id"),s=d.find(m=>m.id===e);if(!s||s.parentId==null)return;const r=d.find(m=>m.id===s.parentId);s.parentId=r?r.parentId:null,v()}),t.off("click",".me-up").on("click",".me-up",function(){F(w,d);const e=$(this).closest(".menu-tree-row").data("id"),s=d.filter(p=>p.parentId===d.find(y=>y.id===e)?.parentId),r=s.findIndex(p=>p.id===e);if(r<=0)return;const m=d.indexOf(s[r]),u=d.indexOf(s[r-1]);[d[m],d[u]]=[d[u],d[m]],v()}),t.off("click",".me-down").on("click",".me-down",function(){F(w,d);const e=$(this).closest(".menu-tree-row").data("id"),s=d.filter(p=>p.parentId===d.find(y=>y.id===e)?.parentId),r=s.findIndex(p=>p.id===e);if(r===-1||r>=s.length-1)return;const m=d.indexOf(s[r]),u=d.indexOf(s[r+1]);[d[m],d[u]]=[d[u],d[m]],v()}),t.off("click",".me-hidden").on("click",".me-hidden",function(){F(w,d);const e=$(this).closest(".menu-tree-row").data("id"),s=d.find(r=>r.id===e);s&&(s.hidden=!s.hidden),v()}),t.off("click",".me-edit").on("click",".me-edit",function(){const e=$(this).closest(".menu-tree-row").data("id"),s=d.find(r=>r.id===e);!s||s.type==="separator"||wt(s,h.resources||[],x,X,()=>v())}),E.tabs(t.find("#me-tabs").get(0)),t.find("#me-variant").val(o.variant||""),t.find("#me-position").val(o.position||"");const C=t.find("#me-appearOnHover").get(0);C&&(C.checked=o.appearOnHover===!0),t.find("#me-float-anchor").val(o.float?.anchor||"TL"),t.find("#me-float-offsetX").val(o.float?.offsetX??""),t.find("#me-float-offsetY").val(o.float?.offsetY??""),t.find("#me-float-width").val(o.float?.width??""),t.find("#me-float-radius").val(o.float?.radius??""),t.find("#me-float-shadow").val(o.float?.shadow||""),t.find("#me-float-opacity").val(o.float?.opacity??"");const R=o.float?.accent||"",W=R.startsWith("#"),A=t.find("#me-float-accent"),_=t.find("#me-float-accent-hex");A.val(W?"__custom__":R),W&&_.val(R);const T=()=>{_.get(0).style.display=A.val()==="__custom__"?"":"none"};T(),A.on("change",T);const I=()=>A.val()==="__custom__"?_.val():A.val(),f=()=>{const e=t.find("#me-position").val()==="floating";t.find(".dm-float-field").each(function(){this.style.display=e?"":"none"}),e&&T();const s=t.find("#me-orientation").val()==="vertical";t.find(".dm-vertical-field").each(function(){this.style.display=s?"":"none"})};t.find("#me-orientation").val(o.orientation||""),t.find("#me-side").val(o.side||"left"),f(),t.find("#me-position").on("change",f),t.find("#me-orientation").on("change",f),t.find("#me-fontFamily").val(o.style?.fontFamily||""),t.find("#me-fontSize").val(o.style?.fontSize||""),t.find("#me-fontWeight").val(o.style?.fontWeight||""),t.find("#me-letterSpacing").val(o.style?.letterSpacing||""),t.find("#me-iconSize").val(o.style?.iconSize||"");const L=t.find("#me-bind-slot"),N=t.find("#me-bind-projects"),P=t.find("#me-bind-pages");L.html('<option value="">\u2014 not bound \u2014</option>'+(D||[]).map(e=>`<option value="${b(e.slot)}">${b(e.label||e.slot)} (${b(e.slot)})</option>`).join("")),N.html(c.map(e=>`<option value="${b(e.slug)}">${b(e.name||e.slug)}</option>`).join("")),L.val(o.binding?.slot||"");const ot=new Set(o.binding?.projects||[]);Array.from(N.get(0).options).forEach(e=>{e.selected=ot.has(e.value)}),P.val((o.binding?.pages||[]).join(`
|
|
29
|
-
`)),t.find("#me-bind-page-pick").html('<option value=""
|
|
28
|
+
`),i(o.id)}}i(null),Domma.icons.scan(t.get(0)),t.get(0).querySelectorAll("[data-tooltip]").forEach(n=>{E.tooltip(n,{content:n.getAttribute("data-tooltip"),position:"top"})})}function F(t,l){t.find(".menu-tree-row").each(function(){const a=$(this).data("id"),i=l.find(k=>k.id===a);if(!i||i.type==="separator")return;const n=$(this).find(".me-text");n.length&&(i.text=n.val());const o=$(this).find(".me-url");o.length&&(i.url=o.val());const c=$(this).find(".me-icon");c.length&&(i.icon=c.val());const h=$(this).find(".me-vis");h.length&&(i.visibility=h.val());const x=$(this).find(".me-perm");x.length&&(i.permission=x.val())})}function _t(t,l){const a={};for(const i of l){const n=t.querySelector(`[name="${i}"]`);n&&(a[i]=n.type==="checkbox"?n.checked:n.value)}return a}function g(t,l={},...a){const i=document.createElement(t);Object.assign(i,l);for(const n of a)n!=null&&i.append(n);return i}function j(t,l,a){const i=g("label",{className:"form-field"},g("span",{className:"form-label",textContent:t}),l);return a&&i.append(g("small",{className:"form-hint",textContent:a,style:"display:block;font-size:.78em;opacity:.7;margin-top:.25rem;line-height:1.35"})),i}function ht(t,l){const a=[["","- Everyone -"],["public","Public - anyone, incl. logged-out"],...(l||[]).map(n=>[n.name,`${n.label||n.name} and above`]),["private","Private - super-admin only"]];t&&!a.some(([n])=>n===t)&&a.push([t,`${t} (custom)`]);const i=B(a,t||"");return i.name="visibility",i}function B(t,l){const a=g("select",{className:"form-select"});return t.forEach(([i,n])=>a.add(new Option(n,i))),a.value=l??"",a}function K(t,l){const a=typeof l=="string"&&l.startsWith("#"),i=B([["","- none -"],["primary","primary"],["success","success"],["danger","danger"],["warning","warning"],["info","info"],["neutral","neutral"],["__custom__","Custom\u2026"]],a?"__custom__":l||""),n=g("input",{type:"color",className:"form-input me-colour-hex",value:a?l:"#000000"});n.style.display=a?"":"none",i.addEventListener("change",()=>{n.style.display=i.value==="__custom__"?"":"none"});const o=j(t,i);return o.append(n),o._read=()=>i.value==="__custom__"?n.value:i.value,o}function yt(t,l){const a=g("input",{className:"form-input me-icon",name:"icon",value:l||""}),i=g("span",{className:"me-icon-preview"});l&&i.setAttribute("data-icon",l);const n=g("button",{type:"button",className:"btn btn-sm btn-ghost",textContent:"Browse\u2026"}),o=c=>{c?i.setAttribute("data-icon",c):i.removeAttribute("data-icon"),Domma.icons.scan(i.parentNode||i)};return n.addEventListener("click",()=>ct(c=>{a.value=c,o(c)})),a.addEventListener("input",()=>o(a.value.trim())),j(t,g("div",{className:"me-icon-field"},a,n,i))}function Z(...t){return g("div",{className:"me-field-grid"},...t)}async function wt(t,l,a,i,n){const o=f=>g("h4",{className:"me-form-section",textContent:f}),c=j("Label",g("input",{className:"form-input",name:"text",value:t.text||""}),"The text shown for this item."),h=j("URL",g("input",{className:"form-input",name:"url",value:t.url||""}),"Where it links. Must start with /, #, https://, or mailto: . Leave blank for a parent that only groups sub-items."),x=yt("Icon",t.icon||""),k=j("Visibility",ht(t.visibility||"",i),"Who can see this item, by role seniority. Picking a role shows it to that role and anyone more senior. Blank = everyone."),D=B([["","- no permission gate -"],...(l||[]).map(f=>[f.key,f.label||f.key])],t.permission||"");D.name="permission";const Y=j("Permission",D,"Hide unless the viewer holds this specific permission. Independent of Visibility - both must pass."),J=j("Badge text",g("input",{className:"form-input",name:"badgeText",value:t.badge?.text||""})),X=B([["","- none -"],...(a||[]).map(f=>[f.slug,f.title||f.slug])],t.badge?.countFrom||"");X.name="badgeCount";const V=j("Badge count from",X,"Show a live count of this collection\u2019s entries as the badge."),U=K("Badge colour",t.badge?.variant||""),d=B([["link","Link"],["pill","Pill"]],t.pill?"pill":"link");d.name="renderAs";const w=j("Render as",d,"Pill draws the item as a rounded chip. Pill styling applies to top-level navbar items."),v=j("Pill style",B([["filled","Filled"],["outline","Outline"]],t.pill?.style||"filled"));v.querySelector("select").name="pillStyle";const z=K("Pill colour",t.pill?.variant||""),C=g("div",{className:"me-pill-wrap"},Z(v,z));C.style.display=t.pill?"":"none",d.addEventListener("change",()=>{C.style.display=d.value==="pill"?"":"none"});const R=K("Text colour",t.colour||""),W=g("button",{className:"btn btn-primary",textContent:"Apply"}),A=g("div",{className:"me-item-form"},o("Link"),c,h,x,o("Access"),Z(k,Y),o("Badge"),Z(J,U),V,o("Appearance"),w,C,R,W),_=E.slideover({title:"Edit menu item",size:"lg",position:"right"});_.element.appendChild(A),_.open();const T=_.element.closest(".dm-slideover")||_.element.querySelector(".dm-slideover")||_.element;T.style.setProperty("width",ut+"px","important"),T.style.setProperty("max-width","95vw","important"),Domma.icons.scan(A);const I=f=>A.querySelector(`[name="${f}"]`)?.value||"";W.addEventListener("click",()=>{t.text=I("text"),t.url=I("url"),t.icon=I("icon"),t.visibility=I("visibility"),t.permission=I("permission");const f=I("badgeText"),L=I("badgeCount"),N=U._read();if(t.badge=f||L?{...f&&{text:f},...L&&{countFrom:L},...N&&{variant:N}}:null,I("renderAs")==="pill"){const P=z._read();t.pill={style:I("pillStyle")||"filled",...P&&{variant:P}}}else t.pill=null;t.colour=R._read(),_.close(),n()})}export const menuEditorView={templateUrl:"/admin/js/templates/menu-editor.html",async onMount(t){const l=location.hash.match(/^#\/menus\/edit\/(.+)$/),a=l?decodeURIComponent(l[1]):null,i=!a,n=E.loader(t.get(0),{type:"dots"});let o,c,h,x,k,D,Y;try{if(i)o={slug:"",name:"",description:"",items:[]};else try{o=await S.menus.get(a)}catch(e){E.toast(`Failed to load menu: ${e.message||e}`,{type:"error"}),location.hash="#/menus";return}c=await S.projects.list().catch(()=>[]),h=await H.get("/api/auth/permissions-registry").catch(()=>({resources:[]})),x=await S.collections.list().catch(()=>[]),k=await S.get("/collections/roles/entries?limit=100").catch(()=>null),D=await S.menuLocations.registry().catch(()=>[]),Y=await S.pages.list().catch(()=>[])}finally{n.destroy()}const X=(Array.isArray(k)?k:k?.entries||[]).map(e=>({name:e.data?.name,label:e.data?.label,level:e.data?.level??99})).filter(e=>e.name).sort((e,s)=>s.level-e.level),V='<option value="">- no permission gate -</option>',U=e=>V+(h.resources||[]).map(s=>{const r=(e||"")===s.key?" selected":"";return`<option value="${s.key}"${r}>${s.label||s.key}</option>`}).join("");t.find("#me-title").text(i?"New menu":`Edit "${o.slug}"`);let d=et(o.items);const w=t.find("#me-items-list");function v(){vt(w,d,U),gt(w,()=>d,v)}v(),t.find("#me-add-item").on("click",()=>{F(w,d),d.push({id:q(),parentId:null,text:"",url:"/",icon:"",visibility:"",permission:"",hidden:!1,bundled:!1}),v()}),t.find("#me-add-separator").on("click",()=>{F(w,d),d.push({id:q(),parentId:null,type:"separator"}),v()});function z(e){return function(){F(w,d);const s=$(this).closest(".menu-tree-row").data("id"),r=d.findIndex(p=>p.id===s);if(r===-1)return;const m=d[r],u=e==="separator"?{id:q(),parentId:m.parentId,type:"separator"}:{id:q(),parentId:m.parentId,text:"",url:"/",icon:"",visibility:"",permission:"",hidden:!1,bundled:!1};d.splice(r+1,0,u),v()}}t.off("click",".me-ins-item").on("click",".me-ins-item",z("item")),t.off("click",".me-ins-sep").on("click",".me-ins-sep",z("separator")),t.off("click",".me-remove").on("click",".me-remove",function(){const e=$(this).closest(".menu-tree-row").data("id");d=d.filter(s=>s.id!==e&&s.parentId!==e),v()}),t.off("click",".me-indent").on("click",".me-indent",function(){F(w,d);const e=$(this).closest(".menu-tree-row").data("id"),s=d.find(u=>u.id===e);if(!s)return;const r=d.filter(u=>u.parentId===s.parentId),m=r.findIndex(u=>u.id===e);m<=0||r[m-1].type!=="separator"&&(s.parentId=r[m-1].id,v())}),t.off("click",".me-outdent").on("click",".me-outdent",function(){F(w,d);const e=$(this).closest(".menu-tree-row").data("id"),s=d.find(m=>m.id===e);if(!s||s.parentId==null)return;const r=d.find(m=>m.id===s.parentId);s.parentId=r?r.parentId:null,v()}),t.off("click",".me-up").on("click",".me-up",function(){F(w,d);const e=$(this).closest(".menu-tree-row").data("id"),s=d.filter(p=>p.parentId===d.find(y=>y.id===e)?.parentId),r=s.findIndex(p=>p.id===e);if(r<=0)return;const m=d.indexOf(s[r]),u=d.indexOf(s[r-1]);[d[m],d[u]]=[d[u],d[m]],v()}),t.off("click",".me-down").on("click",".me-down",function(){F(w,d);const e=$(this).closest(".menu-tree-row").data("id"),s=d.filter(p=>p.parentId===d.find(y=>y.id===e)?.parentId),r=s.findIndex(p=>p.id===e);if(r===-1||r>=s.length-1)return;const m=d.indexOf(s[r]),u=d.indexOf(s[r+1]);[d[m],d[u]]=[d[u],d[m]],v()}),t.off("click",".me-hidden").on("click",".me-hidden",function(){F(w,d);const e=$(this).closest(".menu-tree-row").data("id"),s=d.find(r=>r.id===e);s&&(s.hidden=!s.hidden),v()}),t.off("click",".me-edit").on("click",".me-edit",function(){const e=$(this).closest(".menu-tree-row").data("id"),s=d.find(r=>r.id===e);!s||s.type==="separator"||wt(s,h.resources||[],x,X,()=>v())}),E.tabs(t.find("#me-tabs").get(0)),t.find("#me-variant").val(o.variant||""),t.find("#me-position").val(o.position||"");const C=t.find("#me-appearOnHover").get(0);C&&(C.checked=o.appearOnHover===!0),t.find("#me-float-anchor").val(o.float?.anchor||"TL"),t.find("#me-float-offsetX").val(o.float?.offsetX??""),t.find("#me-float-offsetY").val(o.float?.offsetY??""),t.find("#me-float-width").val(o.float?.width??""),t.find("#me-float-radius").val(o.float?.radius??""),t.find("#me-float-shadow").val(o.float?.shadow||""),t.find("#me-float-opacity").val(o.float?.opacity??"");const R=o.float?.accent||"",W=R.startsWith("#"),A=t.find("#me-float-accent"),_=t.find("#me-float-accent-hex");A.val(W?"__custom__":R),W&&_.val(R);const T=()=>{_.get(0).style.display=A.val()==="__custom__"?"":"none"};T(),A.on("change",T);const I=()=>A.val()==="__custom__"?_.val():A.val(),f=()=>{const e=t.find("#me-position").val()==="floating";t.find(".dm-float-field").each(function(){this.style.display=e?"":"none"}),e&&T();const s=t.find("#me-orientation").val()==="vertical";t.find(".dm-vertical-field").each(function(){this.style.display=s?"":"none"})};t.find("#me-orientation").val(o.orientation||""),t.find("#me-side").val(o.side||"left"),f(),t.find("#me-position").on("change",f),t.find("#me-orientation").on("change",f),t.find("#me-fontFamily").val(o.style?.fontFamily||""),t.find("#me-fontSize").val(o.style?.fontSize||""),t.find("#me-fontWeight").val(o.style?.fontWeight||""),t.find("#me-letterSpacing").val(o.style?.letterSpacing||""),t.find("#me-iconSize").val(o.style?.iconSize||"");const L=t.find("#me-bind-slot"),N=t.find("#me-bind-projects"),P=t.find("#me-bind-pages");L.html('<option value="">- not bound -</option>'+(D||[]).map(e=>`<option value="${b(e.slot)}">${b(e.label||e.slot)} (${b(e.slot)})</option>`).join("")),N.html(c.map(e=>`<option value="${b(e.slug)}">${b(e.name||e.slug)}</option>`).join("")),L.val(o.binding?.slot||"");const ot=new Set(o.binding?.projects||[]);Array.from(N.get(0).options).forEach(e=>{e.selected=ot.has(e.value)}),P.val((o.binding?.pages||[]).join(`
|
|
29
|
+
`)),t.find("#me-bind-page-pick").html('<option value="">- pick a page -</option>'+Y.map(e=>{const s=e.urlPath||e.url||"";return s?`<option value="${b(s)}">${b(e.title||s)} - ${b(s)}</option>`:""}).join("")),t.find("#me-bind-page-add").on("click",()=>{const e=t.find("#me-bind-page-pick").val();if(!e)return;const s=P.val().split(`
|
|
30
30
|
`).map(r=>r.trim()).filter(Boolean);s.includes(e)||s.push(e),P.val(s.join(`
|
|
31
31
|
`)),M()});function Q(){const e=L.val();return e?{slot:e,projects:Array.from(N.get(0).selectedOptions).map(s=>s.value),pages:P.val().split(`
|
|
32
|
-
`).map(s=>s.trim()).filter(Boolean)}:null}function M(){const e=Q(),s=t.find("#me-bind-summary");if(!e){s.html('<span class="text-muted">Not bound
|
|
32
|
+
`).map(s=>s.trim()).filter(Boolean)}:null}function M(){const e=Q(),s=t.find("#me-bind-summary");if(!e){s.html('<span class="text-muted">Not bound - this menu renders only where it is mapped or placed by a shortcode.</span>');return}const r=[];e.projects.length&&r.push(`pages in ${e.projects.map(p=>`<code>${b(p)}</code>`).join(", ")}`),e.pages.length&&r.push(`pages matching ${e.pages.map(p=>`<code>${b(p)}</code>`).join(", ")}`);const m=e.pages.filter(p=>!/^\/[^\s?#*]*(\/\*)?$|^\/\*$/.test(p)),u=m.length?`<div class="text-danger" style="margin-top:.4rem">Not valid page patterns: ${m.map(p=>`<code>${b(p)}</code>`).join(", ")}</div>`:"";s.html(r.length?`Takes over the <code>${b(e.slot)}</code> slot on ${r.join(" and ")}.${u}`:`<span class="text-warning">Slot <code>${b(e.slot)}</code> selected but no project or page chosen - the binding will never match.</span>`)}L.on("change",M),N.on("change",M),P.on("input",M),M();const{getProjectFromHash:st}=await import("../lib/project-context.js"),at=i?st():null,tt=t.find("#me-project");tt.html('<option value="">- none -</option>'+c.map(e=>`<option value="${b(e.slug)}">${b(e.name||e.slug)}</option>`).join("")),t.find("#me-slug").val(o.slug||""),t.find("#me-name").val(o.name||""),t.find("#me-description").val(o.description||""),tt.val(o.meta?.project||at||""),Domma.icons.scan(t.get(0)),t.find("#me-save").on("click",async()=>{F(w,d);const e={variant:t.find("#me-variant").val(),position:t.find("#me-position").val(),appearOnHover:!!(t.find("#me-appearOnHover").get(0)||{}).checked,floatAnchor:t.find("#me-float-anchor").val(),floatOffsetX:t.find("#me-float-offsetX").val(),floatOffsetY:t.find("#me-float-offsetY").val(),floatWidth:t.find("#me-float-width").val(),floatRadius:t.find("#me-float-radius").val(),floatShadow:t.find("#me-float-shadow").val(),floatAccent:I(),floatOpacity:t.find("#me-float-opacity").val(),orientation:t.find("#me-orientation").val(),side:t.find("#me-side").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()},s={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()},r=["fontFamily","fontSize","fontWeight","letterSpacing","iconSize"],m=Object.fromEntries(Object.entries(e).filter(([y,O])=>r.includes(y)&&O)),u={};if(e.position==="floating"){e.floatAnchor&&(u.anchor=e.floatAnchor);for(const[y,O]of[["offsetX",e.floatOffsetX],["offsetY",e.floatOffsetY],["width",e.floatWidth],["radius",e.floatRadius],["opacity",e.floatOpacity]])O!==""&&O!=null&&Number.isFinite(Number(O))&&(u[y]=Number(O));e.floatShadow&&(u.shadow=e.floatShadow),e.floatAccent&&(u.accent=e.floatAccent)}const p={slug:s.slug,name:s.name,description:s.description,...e.variant&&{variant:e.variant},...e.position&&{position:e.position},...e.orientation&&{orientation:e.orientation},...e.orientation==="vertical"&&e.side&&{side:e.side},...Object.keys(u).length&&{float:u},...e.appearOnHover&&{appearOnHover:!0},...Object.keys(m).length&&{style:m},binding:Q(),items:ft(d),meta:{...o.meta||{},project:s.project||null}};try{if(i)await S.menus.create(p);else if(p.slug!==o.slug){await S.menus.create(p);const y=await S.menuLocations.get();let O=!1;for(const[lt,dt]of Object.entries(y))dt===o.slug&&(y[lt]=p.slug,O=!0);O&&await S.menuLocations.save(y),await S.menus.remove(o.slug)}else await S.menus.update(o.slug,p);E.toast("Saved.",{type:"success"}),location.hash="#/menus/edit/"+encodeURIComponent(p.slug)}catch(y){E.toast(`Save failed: ${y.message||y}`,{type:"error"})}})}};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{api as i}from"../api.js";function d(s){if(!Array.isArray(s)||s.length===0)return 0;let c=0;for(const l of s){const r=Array.isArray(l.items)&&l.items.length?d(l.items):0;r>c&&(c=r)}return c+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 c=E.loader(s.get(0),{type:"dots"});let l,r,p,
|
|
1
|
+
import{api as i}from"../api.js";function d(s){if(!Array.isArray(s)||s.length===0)return 0;let c=0;for(const l of s){const r=Array.isArray(l.items)&&l.items.length?d(l.items):0;r>c&&(c=r)}return c+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 c=E.loader(s.get(0),{type:"dots"});let l,r,p,u;try{[l,r,p]=await Promise.all([i.menuLocations.registry().catch(()=>[]),i.menuLocations.get().catch(()=>({})),i.menus.list().catch(()=>[])]),u=await Promise.all(p.map(e=>i.menus.get(e.slug).catch(()=>null)))}finally{c.destroy()}const m={};for(const e of u)e&&e.slug&&(m[e.slug]=d(e.items||[]));const g=(l||[]).map(e=>({slot:e.slot,label:e.label,source:e.source,description:e.description,maxDepth:e.maxDepth,menu:r[e.slot]||""}));T.create("#ml-table",{pageSize:25,data:g,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(p.map(o=>{const h=o.slug===t.menu?" selected":"",y=a(o.name||o.slug);return`<option value="${a(o.slug)}"${h}>${y}</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=m[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"})}})}};
|
package/admin/js/views/menus.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import{api as i}from"../api.js";import{filterByProject as b,getProjectFromHash as y}from"../lib/project-context.js";import{keepTableIconsAlive as f}from"../lib/table-icons.js";function o(
|
|
1
|
+
import{api as i}from"../api.js";import{filterByProject as b,getProjectFromHash as y}from"../lib/project-context.js";import{keepTableIconsAlive as f}from"../lib/table-icons.js";function o(a){return String(a).replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""").replace(/'/g,"'")}export const menusView={templateUrl:"/admin/js/templates/menus.html",async onMount(a){const m=E.loader(a.get(0),{type:"dots"});let r,u;try{[r,u]=await Promise.all([i.menus.list().catch(()=>[]),i.menuLocations.get().catch(()=>({}))])}finally{m.destroy()}const p=y(),g=p?b(r,p):r,l={};for(const[e,t]of Object.entries(u||{}))t&&(l[t]||(l[t]=[]),l[t].push(e));T.create("#menus-table",{pageSize:25,data:g,emptyMessage:'No menus yet. Click "New menu" to create one.',columns:[{key:"slug",title:"Slug",sortable:!0,render:e=>`<code>${o(e)}</code>`},{key:"name",title:"Name",sortable:!0,render:(e,t)=>`<a href="#/menus/edit/${encodeURIComponent(t.slug)}" style="font-weight:600;">${o(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=l[t.slug]||[];return n.length?n.map(s=>`<span class="badge badge-info">${o(s)}</span>`).join(" "):'<span class="text-muted">-</span>'}},{key:"binding",title:"Bound to",render:(e,t)=>{const n=t.binding;if(!n?.slot)return'<span class="text-muted">-</span>';const s=[...(n.projects||[]).map(c=>`project:${c}`),...n.pages||[]],d=s.length?s.join(", "):"nothing - binding never matches";return`<span class="badge badge-warning" data-tooltip="${o(d)}">${o(n.slot)}</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=(l[t.slug]||[]).length>0,s=n?'data-tooltip="Unmap this menu before deleting"':"",d=n?"disabled":"",c=o(t.slug);return`
|
|
2
2
|
<a class="btn btn-sm btn-ghost" href="#/menus/edit/${encodeURIComponent(t.slug)}" data-tooltip="Edit"><span data-icon="edit" data-icon-size="14"></span></a>
|
|
3
3
|
<button class="btn btn-sm btn-ghost btn-duplicate" data-slug="${c}" data-tooltip="Duplicate"><span data-icon="copy" data-icon-size="14"></span></button>
|
|
4
|
-
<button class="btn btn-sm btn-ghost btn-delete" data-slug="${c}" ${d} ${
|
|
5
|
-
`}}]}),Domma.icons.scan("#menus-table"),f(
|
|
4
|
+
<button class="btn btn-sm btn-ghost btn-delete" data-slug="${c}" ${d} ${s}><span data-icon="trash" data-icon-size="14"></span></button>
|
|
5
|
+
`}}]}),Domma.icons.scan("#menus-table"),f(a.get(0)),document.querySelectorAll("#menus-table [data-tooltip]").forEach(e=>{E.tooltip(e,{content:e.getAttribute("data-tooltip"),position:"top"})}),a.off("click",".btn-duplicate").on("click",".btn-duplicate",async function(){const e=$(this).data("slug");try{const t=await i.menus.duplicate(e);E.toast(`Duplicated as "${t.slug}".`,{type:"success"}),location.reload()}catch(t){E.toast(`Duplicate failed: ${t.message||t}`,{type:"error"})}}),a.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 i.menus.remove(e),E.toast("Menu deleted.",{type:"success"}),location.reload()}catch(n){E.toast(`Delete failed: ${n.message||n}`,{type:"error"})}})}};
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import{api as x}from"../api.js";let I=1;const
|
|
1
|
+
import{api as x}from"../api.js";let I=1;const v=t=>String(t||"").replace(/&/g,"&").replace(/"/g,""").replace(/</g,"<");function S(t){const d=[];return(t||[]).forEach(l=>{const o=I++;d.push({_id:o,text:l.text||"",url:l.url||"",icon:l.icon||"",hidden:l.hidden||!1,bundled:l.bundled||!1,parentId:null}),(l.items||l.children||[]).forEach(a=>{d.push({_id:I++,text:a.text||"",url:a.url||"",icon:a.icon||"",hidden:a.hidden||!1,bundled:a.bundled||!1,parentId:o})})}),d}function F(t){return t.filter(d=>d.parentId===null).map(d=>{const l=t.filter(a=>a.parentId===d._id).map(a=>({text:a.text,url:a.url,...a.icon&&{icon:a.icon},...a.hidden&&{hidden:!0},...a.bundled&&{bundled:!0}})),o={text:d.text,url:d.url,...d.icon&&{icon:d.icon},...d.hidden&&{hidden:!0},...d.bundled&&{bundled:!0}};return l.length&&(o.items=l),o})}function A(t){const d=[];return t.filter(l=>l.parentId===null).forEach(l=>{d.push(l),t.filter(o=>o.parentId===l._id).forEach(o=>d.push(o))}),d}export const navigationView={templateUrl:"/admin/js/templates/navigation.html",async onMount(t){let[d,l]=await Promise.all([x.navigation.get().catch(()=>({brand:{},items:[]})),x.settings.get().catch(()=>({}))]),o=S(d.items),a=(l.footer?.links||[]).map(e=>({text:e.text||"",url:e.url||"",...e.hidden&&{hidden:!0}}));const m=()=>{t.find("#nav-items-list .nav-item-row").each(function(){const i=parseInt($(this).data("id"),10),n=o.find(f=>f._id===i);if(!n)return;n.text=$(this).find(".item-text").val(),n.url=$(this).find(".item-url").val(),n.icon=$(this).find(".item-icon").val(),n.hidden=$(this).find(".btn-toggle-hidden").attr("data-hidden")==="true",n.bundled=$(this).find(".btn-toggle-bundled").attr("data-bundled")==="true";const r=$(this).find(".item-parent").val();n.parentId=r?parseInt(r,10):null});const e=new Set(o.filter(i=>i.parentId!==null).map(i=>i._id));o.forEach(i=>{i.parentId!==null&&e.has(i.parentId)&&(i.parentId=null)})},p=()=>{const e=t.find("#nav-items-list").empty(),i=o.filter(n=>n.parentId===null);A(o).forEach(n=>{const r=n.parentId!==null,f=n.hidden===!0,g='<option value="">- top-level -</option>'+i.filter(c=>c._id!==n._id).map(c=>`<option value="${c._id}"${c._id===n.parentId?" selected":""}>${v(c.text)||"(untitled)"}</option>`).join("");e.append(`
|
|
2
2
|
<div class="nav-item-row${r?" nav-item-row--child":""}${f?" nav-item-row--hidden":""}" data-id="${n._id}">
|
|
3
3
|
<span class="nav-col-indent">${r?"\u21B3":""}</span>
|
|
4
|
-
<input type="text" class="form-input item-text nav-col-main" value="${
|
|
5
|
-
<input type="text" class="form-input item-url nav-col-main" value="${
|
|
6
|
-
<input type="text" class="form-input item-icon nav-col-icon" value="${
|
|
4
|
+
<input type="text" class="form-input item-text nav-col-main" value="${v(n.text)}" placeholder="Label">
|
|
5
|
+
<input type="text" class="form-input item-url nav-col-main" value="${v(n.url)}" placeholder="/url">
|
|
6
|
+
<input type="text" class="form-input item-icon nav-col-icon" value="${v(n.icon)}" placeholder="icon">
|
|
7
7
|
<select class="form-select item-parent nav-col-parent">${g}</select>
|
|
8
8
|
<span class="nav-col-action">
|
|
9
9
|
<button class="btn btn-sm btn-ghost btn-toggle-bundled${n.bundled?" active":""}" data-id="${n._id}" data-bundled="${!!n.bundled}" data-tooltip="${n.bundled?"Unbundle":"Bundle (seed on install)"}"><span data-icon="package"></span></button>
|
|
@@ -11,13 +11,13 @@ import{api as x}from"../api.js";let I=1;const u=t=>String(t||"").replace(/&/g,"&
|
|
|
11
11
|
<button class="btn btn-sm btn-ghost btn-remove-item" data-id="${n._id}" data-tooltip="Remove"><span data-icon="trash"></span></button>
|
|
12
12
|
</span>
|
|
13
13
|
</div>
|
|
14
|
-
`)}),Domma.icons.scan("#nav-items-list"),document.querySelectorAll("#nav-items-list [data-tooltip]").forEach(n=>{E.tooltip(n,{content:n.getAttribute("data-tooltip"),position:"top"})})},
|
|
14
|
+
`)}),Domma.icons.scan("#nav-items-list"),document.querySelectorAll("#nav-items-list [data-tooltip]").forEach(n=>{E.tooltip(n,{content:n.getAttribute("data-tooltip"),position:"top"})})},u=()=>{const e=t.find("#footer-links-list").empty();a.forEach((i,n)=>{const r=i.hidden===!0;e.append(`
|
|
15
15
|
<div class="nav-item-row${r?" nav-item-row--hidden":""}" data-footer-idx="${n}">
|
|
16
|
-
<input type="text" class="form-input footer-link-text nav-col-main" value="${
|
|
17
|
-
<input type="text" class="form-input footer-link-url nav-col-main" value="${
|
|
16
|
+
<input type="text" class="form-input footer-link-text nav-col-main" value="${v(i.text)}" placeholder="Label">
|
|
17
|
+
<input type="text" class="form-input footer-link-url nav-col-main" value="${v(i.url)}" placeholder="/url">
|
|
18
18
|
<span class="nav-col-action">
|
|
19
19
|
<button class="btn btn-sm btn-ghost btn-toggle-hidden${r?" active":""}" data-idx="${n}" data-hidden="${r}" data-tooltip="${r?"Show":"Hide"}"><span data-icon="eye-off"></span></button>
|
|
20
20
|
<button class="btn btn-sm btn-ghost btn-remove-footer" data-idx="${n}" data-tooltip="Remove"><span data-icon="trash"></span></button>
|
|
21
21
|
</span>
|
|
22
22
|
</div>
|
|
23
|
-
`)}),Domma.icons.scan("#footer-links-list"),document.querySelectorAll("#footer-links-list [data-tooltip]").forEach(i=>{E.tooltip(i,{content:i.getAttribute("data-tooltip"),position:"top"})})},h=()=>{a=[],t.find("#footer-links-list .nav-item-row").each(function(){const e=$(this).find(".btn-toggle-hidden").attr("data-hidden")==="true",i={text:$(this).find(".footer-link-text").val().trim(),url:$(this).find(".footer-link-url").val().trim()};e&&(i.hidden=!0),a.push(i)})};t.find("#add-footer-link").on("click",()=>{h(),a.push({text:"",url:"",hidden:!1}),
|
|
23
|
+
`)}),Domma.icons.scan("#footer-links-list"),document.querySelectorAll("#footer-links-list [data-tooltip]").forEach(i=>{E.tooltip(i,{content:i.getAttribute("data-tooltip"),position:"top"})})},h=()=>{a=[],t.find("#footer-links-list .nav-item-row").each(function(){const e=$(this).find(".btn-toggle-hidden").attr("data-hidden")==="true",i={text:$(this).find(".footer-link-text").val().trim(),url:$(this).find(".footer-link-url").val().trim()};e&&(i.hidden=!0),a.push(i)})};t.find("#add-footer-link").on("click",()=>{h(),a.push({text:"",url:"",hidden:!1}),u()}),t.off("click",".btn-remove-footer").on("click",".btn-remove-footer",function(){h();const e=parseInt($(this).data("idx"),10);a.splice(e,1),u()}),t.find("#field-brand-text").val(d.brand?.text||""),t.find("#field-brand-url").val(d.brand?.url||"/"),t.find("#field-brand-icon").val(d.brand?.icon||""),t.find("#field-nav-variant").val(d.variant||"dark"),t.find("#field-brand-tagline").val(d.brand?.tagline||""),t.find("#field-brand-logo").val(d.brand?.logo||""),t.find("#field-nav-position").val(d.position||"sticky"),t.find("#field-brand-size").val(d.brand?.size||"md"),t.find("#field-nav-font-family").val(d.style?.fontFamily||""),t.find("#field-nav-font-size").val(d.style?.fontSize||""),t.find("#field-nav-font-weight").val(d.style?.fontWeight||""),t.find("#field-nav-letter-spacing").val(d.style?.letterSpacing||""),t.find("#field-nav-icon-size").val(d.style?.iconSize||""),t.find("#field-admin-brand-title").val(l.adminBrand?.title||""),t.find("#field-admin-brand-icon").val(l.adminBrand?.icon||""),t.find("#field-admin-brand-logo").val(l.adminBrand?.logo||""),p(),u(),E.tabs(t.find("#nav-tabs").get(0)),t.find("#add-nav-item").on("click",()=>{m(),o.push({_id:I++,text:"",url:"",icon:"",hidden:!1,parentId:null}),p()}),t.off("click",".btn-remove-item").on("click",".btn-remove-item",function(){m();const e=parseInt($(this).data("id"),10);o=o.filter(i=>i._id!==e&&i.parentId!==e),p()}),t.off("click","#nav-items-list .btn-toggle-bundled").on("click","#nav-items-list .btn-toggle-bundled",function(e){e.stopPropagation(),m();const i=parseInt($(this).data("id"),10),n=o.find(r=>r._id===i);n&&(n.bundled=!n.bundled),p()}),t.off("click","#nav-items-list .btn-toggle-hidden").on("click","#nav-items-list .btn-toggle-hidden",function(e){e.stopPropagation(),m();const i=parseInt($(this).data("id"),10),n=o.find(r=>r._id===i);n&&(n.hidden=!n.hidden),p()}),t.off("click","#footer-links-list .btn-toggle-hidden").on("click","#footer-links-list .btn-toggle-hidden",function(e){e.stopPropagation(),h();const i=parseInt($(this).data("idx"),10);a[i]&&(a[i].hidden=!a[i].hidden),u()}),t.off("change",".item-parent").on("change",".item-parent",function(){m(),p()}),t.find("#save-nav-btn").on("click",async()=>{m(),h();const e=F(o.map(s=>({...s,text:s.text.trim(),url:s.url.trim(),icon:s.icon.trim()}))).filter(s=>s.text||s.url),i=t.find("#field-brand-icon").val().trim(),n=t.find("#field-brand-tagline").val().trim(),r=t.find("#field-brand-logo").val().trim(),f=t.find("#field-brand-size").val(),g={brand:{text:t.find("#field-brand-text").val().trim(),url:t.find("#field-brand-url").val().trim()||"/",...r&&{logo:r},...i&&!r&&{icon:i},...n&&{tagline:n},...f!=="md"&&{size:f}},items:e,variant:t.find("#field-nav-variant").val(),position:t.find("#field-nav-position").val()||"sticky",style:{...t.find("#field-nav-font-family").val()&&{fontFamily:t.find("#field-nav-font-family").val()},...t.find("#field-nav-font-size").val()&&{fontSize:t.find("#field-nav-font-size").val()},...t.find("#field-nav-font-weight").val()&&{fontWeight:t.find("#field-nav-font-weight").val()},...t.find("#field-nav-letter-spacing").val()&&{letterSpacing:t.find("#field-nav-letter-spacing").val()},...t.find("#field-nav-icon-size").val()&&{iconSize:t.find("#field-nav-icon-size").val()}}},c=a.filter(s=>s.text||s.url);try{await x.navigation.save(g),d=g,o=S(d.items),p(),E.toast("Navigation saved.",{type:"success"})}catch(s){console.error("[navigation] save failed:",s),E.toast("Failed to save navigation.",{type:"error"});return}const y=t.find("#field-admin-brand-logo").val().trim(),k=t.find("#field-admin-brand-icon").val().trim(),_=t.find("#field-admin-brand-title").val().trim(),b={..._&&{title:_},...y&&{logo:y},...!y&&k&&{icon:k}};try{await x.settings.save({adminBrand:b,footer:{links:c}}),l={...l,adminBrand:b},a=c,u();const s=b.title||"CMS Admin",B=b.icon||"layout",w=b.logo||"",z=$("#admin-topbar .topbar-brand");w?z.html(`<img src="${w}" class="topbar-brand-logo" alt="${s}"><span class="topbar-brand-text">${s}</span>`):z.html(`<span data-icon="${B}"></span><span class="topbar-brand-text">${s}</span>`),Domma.icons.scan("#admin-topbar .topbar-brand")}catch(s){console.error("[navigation] admin brand / footer save failed:",s),E.toast(`Admin brand save failed: ${s.message||s}`,{type:"error"})}})}};
|