domma-cms 0.36.3 → 0.36.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/admin/css/admin.css +1 -1
- package/admin/js/lib/card-builder.js +3 -3
- package/admin/js/lib/editor-folding.js +1 -1
- package/admin/js/lib/effects-builder.js +2 -2
- package/admin/js/lib/markdown-toolbar.js +3 -3
- package/admin/js/lib/shortcode-modal.js +1 -1
- package/admin/js/lib/timeline-builder.js +2 -2
- package/admin/js/views/actions-list.js +1 -1
- package/admin/js/views/api-endpoint-editor.js +3 -3
- package/admin/js/views/api-endpoints.js +2 -2
- package/admin/js/views/api-tokens.js +4 -4
- package/admin/js/views/blocks.js +5 -5
- package/admin/js/views/collection-editor.js +4 -4
- package/admin/js/views/collection-entries.js +1 -1
- package/admin/js/views/component-editor.js +1 -1
- package/admin/js/views/components.js +2 -2
- package/admin/js/views/effects.js +1 -1
- package/admin/js/views/form-editor.js +7 -7
- package/admin/js/views/layouts.js +1 -1
- package/admin/js/views/login.js +1 -1
- package/admin/js/views/menu-editor.js +8 -8
- package/admin/js/views/menu-locations.js +1 -1
- package/admin/js/views/menus.js +3 -3
- package/admin/js/views/my-profile.js +1 -1
- package/admin/js/views/notifications.js +1 -1
- package/admin/js/views/page-editor.js +29 -29
- package/admin/js/views/project-detail.js +1 -1
- package/admin/js/views/project-editor.js +1 -1
- package/admin/js/views/project-settings.js +1 -1
- package/admin/js/views/projects.js +2 -2
- package/admin/js/views/user-editor.js +1 -1
- 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/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
import{api as f}from"../api.js";const N={narrow:"Narrow",normal:"Normal",wide:"Wide",full:"Full"},B="display:block;font-size:.7rem;font-weight:600;color:var(--dm-text-muted,#aaa);text-transform:uppercase;letter-spacing:.05em;margin-bottom:.25rem;",S="width:100%;padding:.4rem .6rem;background:var(--dm-input-bg,#1a1a1a);border:1px solid var(--dm-border,#333);border-radius:4px;color:var(--dm-text,#eee);font-size:.9em;box-sizing:border-box;";function x(n,
|
|
1
|
+
import{api as f}from"../api.js";const N={narrow:"Narrow",normal:"Normal",wide:"Wide",full:"Full"},B="display:block;font-size:.7rem;font-weight:600;color:var(--dm-text-muted,#aaa);text-transform:uppercase;letter-spacing:.05em;margin-bottom:.25rem;",S="width:100%;padding:.4rem .6rem;background:var(--dm-input-bg,#1a1a1a);border:1px solid var(--dm-border,#333);border-radius:4px;color:var(--dm-text,#eee);font-size:.9em;box-sizing:border-box;";function x(n,t){const e=document.createElement("div");e.style.cssText="display:flex;flex-direction:column;gap:.25rem;";const a=document.createElement("label");return a.style.cssText=B,a.textContent=n,e.appendChild(a),e.appendChild(t),e}function L(n,t){const e=document.createElement("input");return e.type="text",e.placeholder=n||"",e.value=t||"",e.style.cssText=S,e}function k(n,t){const e=document.createElement("label");e.style.cssText="display:flex;align-items:center;gap:.5rem;cursor:pointer;";const a=document.createElement("input");a.type="checkbox",a.checked=!!t;const o=document.createElement("span");return o.style.cssText="font-size:.9em;color:var(--dm-text,#eee);",o.textContent=n,e.appendChild(a),e.appendChild(o),{wrap:e,cb:a}}function F(n){const t=document.createElement("select");return t.style.cssText=S,[["narrow","Narrow (768px)"],["normal","Normal (1100px)"],["wide","Wide (75% of viewport, max 2000px)"],["full","Full width"]].forEach(([e,a])=>{const o=document.createElement("option");o.value=e,o.textContent=a,e===(n||"normal")&&(o.selected=!0),t.appendChild(o)}),t}function D(n){const t=document.createElement("div");t.style.cssText="display:flex;align-items:center;gap:.5rem;flex-wrap:wrap;";const e=document.createElement("input");e.type="color",e.value=n||"#ffffff",e.disabled=!n,e.style.cssText="width:38px;height:32px;padding:2px;border:1px solid var(--dm-border,#333);border-radius:4px;background:transparent;cursor:pointer;";const a=document.createElement("span");a.style.cssText="font-size:.85em;color:var(--dm-text-muted,#aaa);min-width:4rem;",a.textContent=n||"None";const o=document.createElement("button");o.type="button",o.textContent="Choose",o.style.cssText="padding:.25rem .6rem;font-size:.8em;background:transparent;border:1px solid var(--dm-border,#333);border-radius:4px;color:var(--dm-text-muted,#aaa);cursor:pointer;";const r=document.createElement("button");return r.type="button",r.textContent="Clear",r.style.cssText=o.style.cssText,r.disabled=!n,o.addEventListener("click",()=>{e.disabled=!1,r.disabled=!1,e.click()}),e.addEventListener("input",()=>{a.textContent=e.value,r.disabled=!1}),r.addEventListener("click",()=>{e.value="#ffffff",e.disabled=!0,r.disabled=!0,a.textContent="None"}),t.appendChild(e),t.appendChild(a),t.appendChild(o),t.appendChild(r),{wrap:t,getValue(){return e.disabled?"":e.value}}}function O(n,t,e){const a=document.createElement("div");a.className="card preset-card",a.style.cssText="cursor:pointer;transition:box-shadow .15s;",a.addEventListener("mouseenter",()=>{a.style.boxShadow="0 0 0 2px var(--dm-primary,#4f7fff)"}),a.addEventListener("mouseleave",()=>{a.style.boxShadow=""}),a.addEventListener("click",()=>e(n,t));const o=document.createElement("div");o.className="card-header",o.style.cssText="display:flex;align-items:center;gap:.5rem;flex-wrap:wrap;";const r=document.createElement("strong");if(r.textContent=t.label||n,o.appendChild(r),t.builtin){const s=document.createElement("span");s.className="badge",s.textContent="built-in",s.style.cssText="font-size:.65rem;",o.appendChild(s)}const d=document.createElement("span");d.className="badge badge-outline",d.textContent=N[t.width]||"Normal",d.style.cssText="margin-left:auto;font-size:.65rem;",o.appendChild(d),a.appendChild(o);const c=document.createElement("div");if(c.className="card-body",t.description){const s=document.createElement("p");s.className="text-muted",s.style.cssText="font-size:.85em;margin:0 0 .5rem;",s.textContent=t.description,c.appendChild(s)}const l=document.createElement("div");if(l.style.cssText="display:flex;gap:.75rem;flex-wrap:wrap;",[["Navbar",t.navbar],["Footer",t.footer],["Sidebar",t.sidebar]].forEach(([s,u])=>{const p=document.createElement("span");p.style.cssText=`font-size:.75em;color:${u?"var(--dm-success,#4caf50)":"var(--dm-text-muted,#666)"};display:flex;align-items:center;gap:.25rem;`,p.textContent=(u?"\u25CF ":"\u25CB ")+s,l.appendChild(p)}),c.appendChild(l),t.bgColor){const s=document.createElement("span");s.title=t.bgColor,s.style.cssText=`display:inline-block;width:14px;height:14px;border-radius:3px;background:${t.bgColor};border:1px solid var(--dm-border,#333);margin-top:.4rem;vertical-align:middle;`,c.appendChild(s)}return a.appendChild(c),a}function z(n,t,e,a,o){const r=E.slideover({title:e?"New Layout":"Edit Layout",size:"md"}),d=document.createElement("div");d.style.cssText="display:flex;flex-direction:column;gap:1rem;padding:1rem;";const c=L("e.g. Dark Landing",t.label||""),l=L("Optional description",t.description||""),s=F(t.width),u=k("Show Navbar",t.navbar!==!1),p=k("Show Footer",t.footer!==!1),g=k("Show Sidebar",t.sidebar===!0),C=D(t.bgColor||""),b=L("https://...",t.bgImage||""),v=L("e.g. no-hero wide-content",t.class||"");d.appendChild(x("Label",c)),d.appendChild(x("Description",l)),d.appendChild(x("Width",s));const y=document.createElement("div");y.style.cssText="display:flex;gap:1.5rem;flex-wrap:wrap;",y.appendChild(u.wrap),y.appendChild(p.wrap),y.appendChild(g.wrap),d.appendChild(y),d.appendChild(x("Background Colour",C.wrap)),d.appendChild(x("Background Image URL",b)),d.appendChild(x("Custom CSS Class",v));const w=document.createElement("div");w.style.cssText="display:flex;gap:.5rem;flex-wrap:wrap;padding-top:.5rem;border-top:1px solid var(--dm-border,#333);";const h=document.createElement("button");if(h.type="button",h.className="btn btn-primary",h.textContent="Save",w.appendChild(h),!e&&!t.builtin){const i=document.createElement("button");i.type="button",i.className="btn btn-danger",i.style.marginLeft="auto",i.textContent="Delete",i.addEventListener("click",async()=>{if(await E.confirm(`Delete the "${t.label||n}" layout?`))try{await f.layouts.remove(n),E.toast("Layout deleted.",{type:"success"}),r.close(),o(n)}catch(m){E.toast(m?.message||"Failed to delete layout.",{type:"error"})}}),w.appendChild(i)}d.appendChild(w),h.addEventListener("click",async()=>{const i=c.value.trim();if(!i){E.toast("Label is required.",{type:"warning"});return}const T={label:i,description:l.value.trim(),width:s.value,navbar:u.cb.checked,footer:p.cb.checked,sidebar:g.cb.checked,bgColor:C.getValue(),bgImage:b.value.trim(),class:v.value.trim()};try{let m;e?m=await f.layouts.create(T):m=await f.layouts.update(n,T),E.toast("Layout saved.",{type:"success"}),r.close(),a(e?m.key:n,m.preset||{...t,...T})}catch(m){E.toast(m?.message||"Failed to save layout.",{type:"error"})}}),r.setContent(d),r.open()}export const layoutsView={templateUrl:"/admin/js/templates/layouts.html",async onMount(n){E.tabs(n.find("#layouts-tabs").get(0));const t=E.loader(n.get(0),{type:"dots"});let e=await f.layouts.get().catch(()=>({}));const a=await f.layouts.getOptions().catch(()=>({spacerSize:8,spacerClass:""}));t.destroy();const o=n.find("#presets-grid");function r(){o.empty();const l=o.get(0);Object.entries(e).forEach(([s,u])=>{const p=O(s,u,(g,C)=>{z(g,C,!1,(b,v)=>{e[b]={...e[b],...v},r()},b=>{delete e[b],r()})});l.appendChild(p)}),I.scan(l)}r(),n.find("#create-layout-btn").on("click",()=>{z(null,{},!0,(l,s)=>{if(!l){E.toast("Layout saved but key missing \u2014 please refresh.",{type:"warning"});return}e[l]=s,r()},()=>{})});const d=n.find("#spacer-size-input"),c=n.find("#spacer-class-input");d.val(a.spacerSize??8),c.val(a.spacerClass??""),n.find("#save-options-btn").on("click",async()=>{const l=parseInt(d.val(),10)||8,s=c.val().trim();try{await f.layouts.saveOptions({spacerSize:l,spacerClass:s}),E.toast("Layout options saved.",{type:"success"})}catch{E.toast("Failed to save layout options.",{type:"error"})}})}};
|
package/admin/js/views/login.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{api as o,isAuthenticated as u,setAuthData as l}from"../api.js";import{THEMES as p}from"../lib/themes.js";const f={name:{type:"string",required:!0,minLength:2,label:"Full Name",formConfig:{placeholder:"Your name",autocomplete:"name"}},email:{type:"email",required:!0,label:"Email Address",formConfig:{placeholder:"admin@example.com",autocomplete:"email"}},password:{type:"password",required:!0,minLength:8,label:"Password",formConfig:{placeholder:"\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022",autocomplete:"new-password",tooltip:"Minimum 8 characters"}}},h={email:{type:"email",required:!0,label:"Email Address",formConfig:{placeholder:"you@example.com",autocomplete:"email"}},password:{type:"password",required:!0,label:"Password",formConfig:{placeholder:"\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022",autocomplete:"current-password"}}};export const loginView={templateUrl:"/admin/js/templates/login.html",async onMount(e){if(u()){R.navigate("/");return}const a=window.location.hash,t=a.match(/[?&]token=([a-f0-9]{64})/);if(a.startsWith("#/reset-password")&&t){r(e,"reset"),S(e,t[1]),Domma.icons.scan();return}if(a.startsWith("#/reset-password")){R.navigate("/login");return}let s=!1;try{s=(await o.auth.setupStatus()).needsSetup}catch{E.toast("Could not reach the server.",{type:"error"})}s?(r(e,"setup"),y(e)):(r(e,"login"),k(e)),Domma.icons.scan()}};const g={email:{type:"email",required:!0,label:"Email Address",formConfig:{placeholder:"you@example.com",autocomplete:"email"}}},b={password:{type:"password",required:!0,minLength:8,label:"New Password",formConfig:{placeholder:"\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022",autocomplete:"new-password",tooltip:"Minimum 8 characters"}},confirmPassword:{type:"password",required:!0,label:"Confirm Password",formConfig:{placeholder:"\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022",autocomplete:"new-password"}}},w=["setup","onboarding-site","onboarding-theme","onboarding-done","login","forgot","forgot-success","reset"];function r(e,a){w.forEach(t=>e.find(`#${t}-panel`).hide()),e.find(`#${a}-panel`).show(),Domma.icons.scan()}function d(e,a,t){e.find(`#${a}`).text(t).show()}function y(e){F.render("#setup-form-container",f,{},{layout:"stacked",submitText:"Create admin account",onSubmit:async a=>{try{const t=await o.auth.setup(a);l(t),r(e,"onboarding-site"),v(e)}catch(t){return E.toast(t.message||"Setup failed. Please try again.",{type:"error"}),!1}}})}function v(e){e.find("#ob-site-skip").on("click",a=>{a.preventDefault(),r(e,"onboarding-theme"),c(e),m(e)}),e.find("#ob-site-btn").on("click",async()=>{e.find("#ob-site-error").hide();const a=e.find("#ob-title").val().trim(),t=e.find("#ob-tagline").val().trim(),s=e.find("#ob-site-btn").prop("disabled",!0).text("Saving\u2026");try{const i=await o.settings.get();await o.settings.save({...i,title:a||i.title,tagline:t||i.tagline,seo:{...i.seo||{},defaultTitle:a||i.seo&&i.seo.defaultTitle}});const n=await o.navigation.get();await o.navigation.save({...n,brand:{...n.brand||{},text:a||n.brand.text}}),r(e,"onboarding-theme"),c(e),m(e)}catch(i){d(e,"ob-site-error",i.message||"Could not save site details.")}finally{s.prop("disabled",!1).text("Continue")}})}function c(e){const a=e.find("#theme-grid").empty();p.forEach(t=>{const s=$(`
|
|
1
|
+
import{api as o,isAuthenticated as u,setAuthData as l}from"../api.js";import{THEMES as p}from"../lib/themes.js";const f={name:{type:"string",required:!0,minLength:2,label:"Full Name",formConfig:{placeholder:"Your name",autocomplete:"name"}},email:{type:"email",required:!0,label:"Email Address",formConfig:{placeholder:"admin@example.com",autocomplete:"email"}},password:{type:"password",required:!0,minLength:8,label:"Password",formConfig:{placeholder:"\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022",autocomplete:"new-password",tooltip:"Minimum 8 characters"}}},h={email:{type:"email",required:!0,label:"Email Address",formConfig:{placeholder:"you@example.com",autocomplete:"email"}},password:{type:"password",required:!0,label:"Password",formConfig:{placeholder:"\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022",autocomplete:"current-password"}}};export const loginView={templateUrl:"/admin/js/templates/login.html",async onMount(e){if(u()){R.navigate("/");return}const a=window.location.hash,t=a.match(/[?&]token=([a-f0-9]{64})/);if(a.startsWith("#/reset-password")&&t){r(e,"reset"),S(e,t[1]),Domma.icons.scan();return}if(a.startsWith("#/reset-password")){R.navigate("/login");return}let s=!1;const i=E.loader(e.get(0),{type:"dots"});try{s=(await o.auth.setupStatus()).needsSetup}catch{E.toast("Could not reach the server.",{type:"error"})}finally{i.destroy()}s?(r(e,"setup"),y(e)):(r(e,"login"),k(e)),Domma.icons.scan()}};const g={email:{type:"email",required:!0,label:"Email Address",formConfig:{placeholder:"you@example.com",autocomplete:"email"}}},b={password:{type:"password",required:!0,minLength:8,label:"New Password",formConfig:{placeholder:"\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022",autocomplete:"new-password",tooltip:"Minimum 8 characters"}},confirmPassword:{type:"password",required:!0,label:"Confirm Password",formConfig:{placeholder:"\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022",autocomplete:"new-password"}}},w=["setup","onboarding-site","onboarding-theme","onboarding-done","login","forgot","forgot-success","reset"];function r(e,a){w.forEach(t=>e.find(`#${t}-panel`).hide()),e.find(`#${a}-panel`).show(),Domma.icons.scan()}function d(e,a,t){e.find(`#${a}`).text(t).show()}function y(e){F.render("#setup-form-container",f,{},{layout:"stacked",submitText:"Create admin account",onSubmit:async a=>{try{const t=await o.auth.setup(a);l(t),r(e,"onboarding-site"),v(e)}catch(t){return E.toast(t.message||"Setup failed. Please try again.",{type:"error"}),!1}}})}function v(e){e.find("#ob-site-skip").on("click",a=>{a.preventDefault(),r(e,"onboarding-theme"),c(e),m(e)}),e.find("#ob-site-btn").on("click",async()=>{e.find("#ob-site-error").hide();const a=e.find("#ob-title").val().trim(),t=e.find("#ob-tagline").val().trim(),s=e.find("#ob-site-btn").prop("disabled",!0).text("Saving\u2026");try{const i=await o.settings.get();await o.settings.save({...i,title:a||i.title,tagline:t||i.tagline,seo:{...i.seo||{},defaultTitle:a||i.seo&&i.seo.defaultTitle}});const n=await o.navigation.get();await o.navigation.save({...n,brand:{...n.brand||{},text:a||n.brand.text}}),r(e,"onboarding-theme"),c(e),m(e)}catch(i){d(e,"ob-site-error",i.message||"Could not save site details.")}finally{s.prop("disabled",!1).text("Continue")}})}function c(e){const a=e.find("#theme-grid").empty();p.forEach(t=>{const s=$(`
|
|
2
2
|
<div class="theme-swatch" data-theme="${t.id}" title="${t.id}">
|
|
3
3
|
<div class="theme-swatch-preview" style="background:${t.bg}">
|
|
4
4
|
<div class="theme-swatch-accent" style="background:${t.primary}"></div>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import{api as
|
|
2
|
-
<div class="menu-tree-row menu-tree-row--separator" data-id="${
|
|
1
|
+
import{api as k}from"../api.js";import{colourToCss as U}from"/public/js/menu-decor.mjs";import{openIconPicker as V}from"../lib/card-builder.js";let G=1;const N=()=>"i_"+G++,K=760;function q(t,r=null,l=[]){for(const e of t||[]){const n=N();if(e&&e.type==="separator"){l.push({id:n,parentId:r,type:"separator"});continue}l.push({id:n,parentId:r,text:e.text||"",url:e.url||"",icon:e.icon||"",visibility:e.visibility||"",permission:e.permission||"",hidden:!!e.hidden,bundled:!!e.bundled,badge:e.badge?{...e.badge}:null,pill:e.pill?{...e.pill}:null,colour:e.colour||""}),Array.isArray(e.items)&&e.items.length&&q(e.items,n,l)}return l}function X(t){const r=new Map;for(const e of t)r.has(e.parentId)||r.set(e.parentId,[]),r.get(e.parentId).push(e);function l(e){return(r.get(e)||[]).map(n=>{if(n.type==="separator")return{type:"separator"};const i=l(n.id);return{text:(n.text||"").trim(),url:(n.url||"").trim(),...n.icon&&{icon:n.icon.trim()},...n.visibility&&{visibility:n.visibility},...n.permission&&{permission:n.permission},...n.hidden&&{hidden:!0},...n.bundled&&{bundled:!0},...n.badge&&(n.badge.text||n.badge.countFrom)?{badge:{...n.badge.text&&{text:n.badge.text},...n.badge.countFrom&&{countFrom:n.badge.countFrom},...n.badge.variant&&{variant:n.badge.variant}}}:{},...n.pill&&n.pill.variant?{pill:{style:n.pill.style||"filled",variant:n.pill.variant}}:{},...n.colour&&{colour:n.colour},...i.length&&{items:i}}})}return l(null)}function O(t){return String(t||"").replace(/&/g,"&").replace(/"/g,""").replace(/</g,"<")}function J(t,r){let l=0,e=r.find(n=>n.id===t);for(;e&&e.parentId;)l++,e=r.find(n=>n.id===e.parentId);return l}function v(t,r,l=()=>""){t.html("");function e(n){for(const i of r.filter(b=>b.parentId===n)){const b=J(i.id,r);if(i.type==="separator"){t.append(`
|
|
2
|
+
<div class="menu-tree-row menu-tree-row--separator" data-id="${i.id}" style="margin-left:${b*28}px">
|
|
3
3
|
<span class="menu-tree-grip" data-icon="move"></span>
|
|
4
4
|
<span class="me-sep-line"><span class="me-sep-label">Separator</span></span>
|
|
5
5
|
<button class="btn btn-sm btn-ghost me-ins-item" data-tooltip="Add item below"><span data-icon="plus"></span></button>
|
|
@@ -10,19 +10,19 @@ import{api as x}from"../api.js";import{colourToCss as U}from"/public/js/menu-dec
|
|
|
10
10
|
<button class="btn btn-sm btn-ghost me-down" data-tooltip="Move down"><span data-icon="arrow-down"></span></button>
|
|
11
11
|
<button class="btn btn-sm btn-danger me-remove" data-tooltip="Remove"><span data-icon="trash"></span></button>
|
|
12
12
|
</div>
|
|
13
|
-
`);continue}const
|
|
14
|
-
<div class="menu-tree-row menu-tree-row--slim${
|
|
13
|
+
`);continue}const y=i.badge&&(i.badge.text||i.badge.countFrom)?`<span class="dm-menu-badge">${O(String(i.badge.text||"#"))}</span>`:"",w=U(i.colour),x=w?` style="color:${O(w)}"`:"";t.append(`
|
|
14
|
+
<div class="menu-tree-row menu-tree-row--slim${i.hidden?" menu-tree-row--hidden":""}" data-id="${i.id}" style="margin-left:${b*28}px">
|
|
15
15
|
<span class="menu-tree-grip" data-icon="move"></span>
|
|
16
|
-
<span class="me-row-label"${
|
|
17
|
-
<span class="me-row-url">${O(
|
|
16
|
+
<span class="me-row-label"${x}>${O(i.text||"(untitled)")}${y}</span>
|
|
17
|
+
<span class="me-row-url">${O(i.url||"")}</span>
|
|
18
18
|
<button class="btn btn-sm btn-ghost me-ins-item" data-tooltip="Add item below"><span data-icon="plus"></span></button>
|
|
19
19
|
<button class="btn btn-sm btn-ghost me-ins-sep" data-tooltip="Add separator below"><span data-icon="minus"></span></button>
|
|
20
20
|
<button class="btn btn-sm btn-ghost me-outdent" data-tooltip="Outdent"><span data-icon="chevron-left"></span></button>
|
|
21
21
|
<button class="btn btn-sm btn-ghost me-indent" data-tooltip="Indent"><span data-icon="chevron-right"></span></button>
|
|
22
22
|
<button class="btn btn-sm btn-ghost me-up" data-tooltip="Move up"><span data-icon="arrow-up"></span></button>
|
|
23
23
|
<button class="btn btn-sm btn-ghost me-down" data-tooltip="Move down"><span data-icon="arrow-down"></span></button>
|
|
24
|
-
<button class="btn btn-sm btn-ghost me-hidden ${
|
|
24
|
+
<button class="btn btn-sm btn-ghost me-hidden ${i.hidden?"active":""}" data-tooltip="${i.hidden?"Show":"Hide"}"><span data-icon="eye-off"></span></button>
|
|
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-danger me-remove" data-tooltip="Remove"><span data-icon="trash"></span></button>
|
|
27
27
|
</div>
|
|
28
|
-
`),n(r.id)}}n(null),Domma.icons.scan(t.get(0)),t.get(0).querySelectorAll("[data-tooltip]").forEach(e=>{E.tooltip(e,{content:e.getAttribute("data-tooltip"),position:"top"})})}function I(t,l){t.find(".menu-tree-row").each(function(){const a=$(this).data("id"),n=l.find(F=>F.id===a);if(!n||n.type==="separator")return;const e=$(this).find(".me-text");e.length&&(n.text=e.val());const r=$(this).find(".me-url");r.length&&(n.url=r.val());const f=$(this).find(".me-icon");f.length&&(n.icon=f.val());const S=$(this).find(".me-vis");S.length&&(n.visibility=S.val());const y=$(this).find(".me-perm");y.length&&(n.permission=y.val())})}function it(t,l){const a={};for(const n of l){const e=t.querySelector(`[name="${n}"]`);e&&(a[n]=e.type==="checkbox"?e.checked:e.value)}return a}function h(t,l={},...a){const n=document.createElement(t);Object.assign(n,l);for(const e of a)e!=null&&n.append(e);return n}function k(t,l,a){const n=h("label",{className:"form-field"},h("span",{className:"form-label",textContent:t}),l);return a&&n.append(h("small",{className:"form-hint",textContent:a,style:"display:block;font-size:.78em;opacity:.7;margin-top:.25rem;line-height:1.35"})),n}function Q(t,l){const a=[["","\u2014 Everyone \u2014"],["public","Public \u2014 anyone, incl. logged-out"],...(l||[]).map(e=>[e.name,`${e.label||e.name} and above`]),["private","Private \u2014 super-admin only"]];t&&!a.some(([e])=>e===t)&&a.push([t,`${t} (custom)`]);const n=P(a,t||"");return n.name="visibility",n}function P(t,l){const a=h("select",{className:"form-select"});return t.forEach(([n,e])=>a.add(new Option(e,n))),a.value=l??"",a}function B(t,l){const a=typeof l=="string"&&l.startsWith("#"),n=P([["","\u2014 none \u2014"],["primary","primary"],["success","success"],["danger","danger"],["warning","warning"],["info","info"],["neutral","neutral"],["__custom__","Custom\u2026"]],a?"__custom__":l||""),e=h("input",{type:"color",className:"form-input me-colour-hex",value:a?l:"#000000"});e.style.display=a?"":"none",n.addEventListener("change",()=>{e.style.display=n.value==="__custom__"?"":"none"});const r=k(t,n);return r.append(e),r._read=()=>n.value==="__custom__"?e.value:n.value,r}function Y(t,l){const a=h("input",{className:"form-input me-icon",name:"icon",value:l||""}),n=h("span",{className:"me-icon-preview"});l&&n.setAttribute("data-icon",l);const e=h("button",{type:"button",className:"btn btn-sm btn-ghost",textContent:"Browse\u2026"}),r=f=>{f?n.setAttribute("data-icon",f):n.removeAttribute("data-icon"),Domma.icons.scan(n.parentNode||n)};return e.addEventListener("click",()=>V(f=>{a.value=f,r(f)})),a.addEventListener("input",()=>r(a.value.trim())),k(t,h("div",{className:"me-icon-field"},a,e,n))}function W(...t){return h("div",{className:"me-field-grid"},...t)}async function Z(t,l,a,n,e){const r=g=>h("h4",{className:"me-form-section",textContent:g}),f=k("Label",h("input",{className:"form-input",name:"text",value:t.text||""}),"The text shown for this item."),S=k("URL",h("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."),y=Y("Icon",t.icon||""),F=k("Visibility",Q(t.visibility||"",n),"Who can see this item, by role seniority. Picking a role shows it to that role and anyone more senior. Blank = everyone."),L=P([["","\u2014 no permission gate \u2014"],...(l||[]).map(g=>[g.key,g.label||g.key])],t.permission||"");L.name="permission";const z=k("Permission",L,"Hide unless the viewer holds this specific permission. Independent of Visibility \u2014 both must pass."),v=k("Badge text",h("input",{className:"form-input",name:"badgeText",value:t.badge?.text||""})),i=P([["","\u2014 none \u2014"],...(a||[]).map(g=>[g.slug,g.title||g.slug])],t.badge?.countFrom||"");i.name="badgeCount";const m=k("Badge count from",i,"Show a live count of this collection\u2019s entries as the badge."),A=B("Badge colour",t.badge?.variant||""),_=P([["link","Link"],["pill","Pill"]],t.pill?"pill":"link");_.name="renderAs";const T=k("Render as",_,"Pill draws the item as a rounded chip. Pill styling applies to top-level navbar items."),R=k("Pill style",P([["filled","Filled"],["outline","Outline"]],t.pill?.style||"filled"));R.querySelector("select").name="pillStyle";const N=B("Pill colour",t.pill?.variant||""),o=h("div",{className:"me-pill-wrap"},W(R,N));o.style.display=t.pill?"":"none",_.addEventListener("change",()=>{o.style.display=_.value==="pill"?"":"none"});const s=B("Text colour",t.colour||""),d=h("button",{className:"btn btn-primary",textContent:"Apply"}),p=h("div",{className:"me-item-form"},r("Link"),f,S,y,r("Access"),W(F,z),r("Badge"),W(v,A),m,r("Appearance"),T,o,s,d),c=E.slideover({title:"Edit menu item",size:"lg",position:"right"});c.element.appendChild(p),c.open();const u=c.element.closest(".dm-slideover")||c.element.querySelector(".dm-slideover")||c.element;u.style.setProperty("width",K+"px","important"),u.style.setProperty("max-width","95vw","important"),Domma.icons.scan(p);const b=g=>p.querySelector(`[name="${g}"]`)?.value||"";d.addEventListener("click",()=>{t.text=b("text"),t.url=b("url"),t.icon=b("icon"),t.visibility=b("visibility"),t.permission=b("permission");const g=b("badgeText"),j=b("badgeCount"),M=A._read();if(t.badge=g||j?{...g&&{text:g},...j&&{countFrom:j},...M&&{variant:M}}:null,b("renderAs")==="pill"){const D=N._read();t.pill={style:b("pillStyle")||"filled",...D&&{variant:D}}}else t.pill=null;t.colour=s._read(),c.close(),e()})}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,n=!a;let e;if(n)e={slug:"",name:"",description:"",items:[]};else try{e=await x.menus.get(a)}catch(o){E.toast(`Failed to load menu: ${o.message||o}`,{type:"error"}),location.hash="#/menus";return}const r=await x.projects.list().catch(()=>[]),f=await H.get("/api/auth/permissions-registry").catch(()=>({resources:[]})),S=await x.collections.list().catch(()=>[]),y=await x.get("/collections/roles/entries?limit=100").catch(()=>null),L=(Array.isArray(y)?y:y?.entries||[]).map(o=>({name:o.data?.name,label:o.data?.label,level:o.data?.level??99})).filter(o=>o.name).sort((o,s)=>s.level-o.level),z='<option value="">\u2014 no permission gate \u2014</option>',v=o=>z+(f.resources||[]).map(s=>{const d=(o||"")===s.key?" selected":"";return`<option value="${s.key}"${d}>${s.label||s.key}</option>`}).join("");t.find("#me-title").text(n?"New menu":`Edit "${e.slug}"`);let i=q(e.items);const m=t.find("#me-items-list");w(m,i,v),t.find("#me-add-item").on("click",()=>{I(m,i),i.push({id:C(),parentId:null,text:"",url:"/",icon:"",visibility:"",permission:"",hidden:!1,bundled:!1}),w(m,i,v)}),t.find("#me-add-separator").on("click",()=>{I(m,i),i.push({id:C(),parentId:null,type:"separator"}),w(m,i,v)});function A(o){return function(){I(m,i);const s=$(this).closest(".menu-tree-row").data("id"),d=i.findIndex(u=>u.id===s);if(d===-1)return;const p=i[d],c=o==="separator"?{id:C(),parentId:p.parentId,type:"separator"}:{id:C(),parentId:p.parentId,text:"",url:"/",icon:"",visibility:"",permission:"",hidden:!1,bundled:!1};i.splice(d+1,0,c),w(m,i,v)}}t.off("click",".me-ins-item").on("click",".me-ins-item",A("item")),t.off("click",".me-ins-sep").on("click",".me-ins-sep",A("separator")),t.off("click",".me-remove").on("click",".me-remove",function(){const o=$(this).closest(".menu-tree-row").data("id");i=i.filter(s=>s.id!==o&&s.parentId!==o),w(m,i,v)}),t.off("click",".me-indent").on("click",".me-indent",function(){I(m,i);const o=$(this).closest(".menu-tree-row").data("id"),s=i.find(c=>c.id===o);if(!s)return;const d=i.filter(c=>c.parentId===s.parentId),p=d.findIndex(c=>c.id===o);p<=0||d[p-1].type!=="separator"&&(s.parentId=d[p-1].id,w(m,i,v))}),t.off("click",".me-outdent").on("click",".me-outdent",function(){I(m,i);const o=$(this).closest(".menu-tree-row").data("id"),s=i.find(p=>p.id===o);if(!s||s.parentId==null)return;const d=i.find(p=>p.id===s.parentId);s.parentId=d?d.parentId:null,w(m,i,v)}),t.off("click",".me-up").on("click",".me-up",function(){I(m,i);const o=$(this).closest(".menu-tree-row").data("id"),s=i.filter(u=>u.parentId===i.find(b=>b.id===o)?.parentId),d=s.findIndex(u=>u.id===o);if(d<=0)return;const p=i.indexOf(s[d]),c=i.indexOf(s[d-1]);[i[p],i[c]]=[i[c],i[p]],w(m,i,v)}),t.off("click",".me-down").on("click",".me-down",function(){I(m,i);const o=$(this).closest(".menu-tree-row").data("id"),s=i.filter(u=>u.parentId===i.find(b=>b.id===o)?.parentId),d=s.findIndex(u=>u.id===o);if(d===-1||d>=s.length-1)return;const p=i.indexOf(s[d]),c=i.indexOf(s[d+1]);[i[p],i[c]]=[i[c],i[p]],w(m,i,v)}),t.off("click",".me-hidden").on("click",".me-hidden",function(){I(m,i);const o=$(this).closest(".menu-tree-row").data("id"),s=i.find(d=>d.id===o);s&&(s.hidden=!s.hidden),w(m,i,v)}),t.off("click",".me-edit").on("click",".me-edit",function(){const o=$(this).closest(".menu-tree-row").data("id"),s=i.find(d=>d.id===o);!s||s.type==="separator"||Z(s,f.resources||[],S,L,()=>w(m,i,v))}),E.tabs(t.find("#me-tabs").get(0)),t.find("#me-variant").val(e.variant||""),t.find("#me-position").val(e.position||"");const _=t.find("#me-appearOnHover").get(0);_&&(_.checked=e.appearOnHover===!0),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:T}=await import("../lib/project-context.js"),R=n?T():null,N=t.find("#me-project");N.html('<option value="">\u2014 none \u2014</option>'+r.map(o=>`<option value="${O(o.slug)}">${O(o.name||o.slug)}</option>`).join("")),t.find("#me-slug").val(e.slug||""),t.find("#me-name").val(e.name||""),t.find("#me-description").val(e.description||""),N.val(e.meta?.project||R||""),Domma.icons.scan(t.get(0)),t.find("#me-save").on("click",async()=>{I(m,i);const o={variant:t.find("#me-variant").val(),position:t.find("#me-position").val(),appearOnHover:!!(t.find("#me-appearOnHover").get(0)||{}).checked,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()},d=["fontFamily","fontSize","fontWeight","letterSpacing","iconSize"],p=Object.fromEntries(Object.entries(o).filter(([u,b])=>d.includes(u)&&b)),c={slug:s.slug,name:s.name,description:s.description,...o.variant&&{variant:o.variant},...o.position&&{position:o.position},...o.appearOnHover&&{appearOnHover:!0},...Object.keys(p).length&&{style:p},items:X(i),meta:{...e.meta||{},project:s.project||null}};try{if(n)await x.menus.create(c);else if(c.slug!==e.slug){await x.menus.create(c);const u=await x.menuLocations.get();let b=!1;for(const[g,j]of Object.entries(u))j===e.slug&&(u[g]=c.slug,b=!0);b&&await x.menuLocations.save(u),await x.menus.remove(e.slug)}else await x.menus.update(e.slug,c);E.toast("Saved.",{type:"success"}),location.hash="#/menus/edit/"+encodeURIComponent(c.slug)}catch(u){E.toast(`Save failed: ${u.message||u}`,{type:"error"})}})}};
|
|
28
|
+
`),e(i.id)}}e(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 S(t,r){t.find(".menu-tree-row").each(function(){const l=$(this).data("id"),e=r.find(x=>x.id===l);if(!e||e.type==="separator")return;const n=$(this).find(".me-text");n.length&&(e.text=n.val());const i=$(this).find(".me-url");i.length&&(e.url=i.val());const b=$(this).find(".me-icon");b.length&&(e.icon=b.val());const y=$(this).find(".me-vis");y.length&&(e.visibility=y.val());const w=$(this).find(".me-perm");w.length&&(e.permission=w.val())})}function it(t,r){const l={};for(const e of r){const n=t.querySelector(`[name="${e}"]`);n&&(l[e]=n.type==="checkbox"?n.checked:n.value)}return l}function g(t,r={},...l){const e=document.createElement(t);Object.assign(e,r);for(const n of l)n!=null&&e.append(n);return e}function I(t,r,l){const e=g("label",{className:"form-field"},g("span",{className:"form-label",textContent:t}),r);return l&&e.append(g("small",{className:"form-hint",textContent:l,style:"display:block;font-size:.78em;opacity:.7;margin-top:.25rem;line-height:1.35"})),e}function Q(t,r){const l=[["","\u2014 Everyone \u2014"],["public","Public \u2014 anyone, incl. logged-out"],...(r||[]).map(n=>[n.name,`${n.label||n.name} and above`]),["private","Private \u2014 super-admin only"]];t&&!l.some(([n])=>n===t)&&l.push([t,`${t} (custom)`]);const e=P(l,t||"");return e.name="visibility",e}function P(t,r){const l=g("select",{className:"form-select"});return t.forEach(([e,n])=>l.add(new Option(n,e))),l.value=r??"",l}function W(t,r){const l=typeof r=="string"&&r.startsWith("#"),e=P([["","\u2014 none \u2014"],["primary","primary"],["success","success"],["danger","danger"],["warning","warning"],["info","info"],["neutral","neutral"],["__custom__","Custom\u2026"]],l?"__custom__":r||""),n=g("input",{type:"color",className:"form-input me-colour-hex",value:l?r:"#000000"});n.style.display=l?"":"none",e.addEventListener("change",()=>{n.style.display=e.value==="__custom__"?"":"none"});const i=I(t,e);return i.append(n),i._read=()=>e.value==="__custom__"?n.value:e.value,i}function Y(t,r){const l=g("input",{className:"form-input me-icon",name:"icon",value:r||""}),e=g("span",{className:"me-icon-preview"});r&&e.setAttribute("data-icon",r);const n=g("button",{type:"button",className:"btn btn-sm btn-ghost",textContent:"Browse\u2026"}),i=b=>{b?e.setAttribute("data-icon",b):e.removeAttribute("data-icon"),Domma.icons.scan(e.parentNode||e)};return n.addEventListener("click",()=>V(b=>{l.value=b,i(b)})),l.addEventListener("input",()=>i(l.value.trim())),I(t,g("div",{className:"me-icon-field"},l,n,e))}function M(...t){return g("div",{className:"me-field-grid"},...t)}async function Z(t,r,l,e,n){const i=m=>g("h4",{className:"me-form-section",textContent:m}),b=I("Label",g("input",{className:"form-input",name:"text",value:t.text||""}),"The text shown for this item."),y=I("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."),w=Y("Icon",t.icon||""),x=I("Visibility",Q(t.visibility||"",e),"Who can see this item, by role seniority. Picking a role shows it to that role and anyone more senior. Blank = everyone."),z=P([["","\u2014 no permission gate \u2014"],...(r||[]).map(m=>[m.key,m.label||m.key])],t.permission||"");z.name="permission";const T=I("Permission",z,"Hide unless the viewer holds this specific permission. Independent of Visibility \u2014 both must pass."),B=I("Badge text",g("input",{className:"form-input",name:"badgeText",value:t.badge?.text||""})),h=P([["","\u2014 none \u2014"],...(l||[]).map(m=>[m.slug,m.title||m.slug])],t.badge?.countFrom||"");h.name="badgeCount";const o=I("Badge count from",h,"Show a live count of this collection\u2019s entries as the badge."),u=W("Badge colour",t.badge?.variant||""),_=P([["link","Link"],["pill","Pill"]],t.pill?"pill":"link");_.name="renderAs";const j=I("Render as",_,"Pill draws the item as a rounded chip. Pill styling applies to top-level navbar items."),C=I("Pill style",P([["filled","Filled"],["outline","Outline"]],t.pill?.style||"filled"));C.querySelector("select").name="pillStyle";const L=W("Pill colour",t.pill?.variant||""),F=g("div",{className:"me-pill-wrap"},M(C,L));F.style.display=t.pill?"":"none",_.addEventListener("change",()=>{F.style.display=_.value==="pill"?"":"none"});const s=W("Text colour",t.colour||""),a=g("button",{className:"btn btn-primary",textContent:"Apply"}),d=g("div",{className:"me-item-form"},i("Link"),b,y,w,i("Access"),M(x,T),i("Badge"),M(B,u),o,i("Appearance"),j,F,s,a),p=E.slideover({title:"Edit menu item",size:"lg",position:"right"});p.element.appendChild(d),p.open();const f=p.element.closest(".dm-slideover")||p.element.querySelector(".dm-slideover")||p.element;f.style.setProperty("width",K+"px","important"),f.style.setProperty("max-width","95vw","important"),Domma.icons.scan(d);const c=m=>d.querySelector(`[name="${m}"]`)?.value||"";a.addEventListener("click",()=>{t.text=c("text"),t.url=c("url"),t.icon=c("icon"),t.visibility=c("visibility"),t.permission=c("permission");const m=c("badgeText"),A=c("badgeCount"),R=u._read();if(t.badge=m||A?{...m&&{text:m},...A&&{countFrom:A},...R&&{variant:R}}:null,c("renderAs")==="pill"){const D=L._read();t.pill={style:c("pillStyle")||"filled",...D&&{variant:D}}}else t.pill=null;t.colour=s._read(),p.close(),n()})}export const menuEditorView={templateUrl:"/admin/js/templates/menu-editor.html",async onMount(t){const r=location.hash.match(/^#\/menus\/edit\/(.+)$/),l=r?decodeURIComponent(r[1]):null,e=!l,n=E.loader(t.get(0),{type:"dots"});let i,b,y,w,x;try{if(e)i={slug:"",name:"",description:"",items:[]};else try{i=await k.menus.get(l)}catch(s){E.toast(`Failed to load menu: ${s.message||s}`,{type:"error"}),location.hash="#/menus";return}b=await k.projects.list().catch(()=>[]),y=await H.get("/api/auth/permissions-registry").catch(()=>({resources:[]})),w=await k.collections.list().catch(()=>[]),x=await k.get("/collections/roles/entries?limit=100").catch(()=>null)}finally{n.destroy()}const T=(Array.isArray(x)?x:x?.entries||[]).map(s=>({name:s.data?.name,label:s.data?.label,level:s.data?.level??99})).filter(s=>s.name).sort((s,a)=>a.level-s.level),B='<option value="">\u2014 no permission gate \u2014</option>',h=s=>B+(y.resources||[]).map(a=>{const d=(s||"")===a.key?" selected":"";return`<option value="${a.key}"${d}>${a.label||a.key}</option>`}).join("");t.find("#me-title").text(e?"New menu":`Edit "${i.slug}"`);let o=q(i.items);const u=t.find("#me-items-list");v(u,o,h),t.find("#me-add-item").on("click",()=>{S(u,o),o.push({id:N(),parentId:null,text:"",url:"/",icon:"",visibility:"",permission:"",hidden:!1,bundled:!1}),v(u,o,h)}),t.find("#me-add-separator").on("click",()=>{S(u,o),o.push({id:N(),parentId:null,type:"separator"}),v(u,o,h)});function _(s){return function(){S(u,o);const a=$(this).closest(".menu-tree-row").data("id"),d=o.findIndex(c=>c.id===a);if(d===-1)return;const p=o[d],f=s==="separator"?{id:N(),parentId:p.parentId,type:"separator"}:{id:N(),parentId:p.parentId,text:"",url:"/",icon:"",visibility:"",permission:"",hidden:!1,bundled:!1};o.splice(d+1,0,f),v(u,o,h)}}t.off("click",".me-ins-item").on("click",".me-ins-item",_("item")),t.off("click",".me-ins-sep").on("click",".me-ins-sep",_("separator")),t.off("click",".me-remove").on("click",".me-remove",function(){const s=$(this).closest(".menu-tree-row").data("id");o=o.filter(a=>a.id!==s&&a.parentId!==s),v(u,o,h)}),t.off("click",".me-indent").on("click",".me-indent",function(){S(u,o);const s=$(this).closest(".menu-tree-row").data("id"),a=o.find(f=>f.id===s);if(!a)return;const d=o.filter(f=>f.parentId===a.parentId),p=d.findIndex(f=>f.id===s);p<=0||d[p-1].type!=="separator"&&(a.parentId=d[p-1].id,v(u,o,h))}),t.off("click",".me-outdent").on("click",".me-outdent",function(){S(u,o);const s=$(this).closest(".menu-tree-row").data("id"),a=o.find(p=>p.id===s);if(!a||a.parentId==null)return;const d=o.find(p=>p.id===a.parentId);a.parentId=d?d.parentId:null,v(u,o,h)}),t.off("click",".me-up").on("click",".me-up",function(){S(u,o);const s=$(this).closest(".menu-tree-row").data("id"),a=o.filter(c=>c.parentId===o.find(m=>m.id===s)?.parentId),d=a.findIndex(c=>c.id===s);if(d<=0)return;const p=o.indexOf(a[d]),f=o.indexOf(a[d-1]);[o[p],o[f]]=[o[f],o[p]],v(u,o,h)}),t.off("click",".me-down").on("click",".me-down",function(){S(u,o);const s=$(this).closest(".menu-tree-row").data("id"),a=o.filter(c=>c.parentId===o.find(m=>m.id===s)?.parentId),d=a.findIndex(c=>c.id===s);if(d===-1||d>=a.length-1)return;const p=o.indexOf(a[d]),f=o.indexOf(a[d+1]);[o[p],o[f]]=[o[f],o[p]],v(u,o,h)}),t.off("click",".me-hidden").on("click",".me-hidden",function(){S(u,o);const s=$(this).closest(".menu-tree-row").data("id"),a=o.find(d=>d.id===s);a&&(a.hidden=!a.hidden),v(u,o,h)}),t.off("click",".me-edit").on("click",".me-edit",function(){const s=$(this).closest(".menu-tree-row").data("id"),a=o.find(d=>d.id===s);!a||a.type==="separator"||Z(a,y.resources||[],w,T,()=>v(u,o,h))}),E.tabs(t.find("#me-tabs").get(0)),t.find("#me-variant").val(i.variant||""),t.find("#me-position").val(i.position||"");const j=t.find("#me-appearOnHover").get(0);j&&(j.checked=i.appearOnHover===!0),t.find("#me-fontFamily").val(i.style?.fontFamily||""),t.find("#me-fontSize").val(i.style?.fontSize||""),t.find("#me-fontWeight").val(i.style?.fontWeight||""),t.find("#me-letterSpacing").val(i.style?.letterSpacing||""),t.find("#me-iconSize").val(i.style?.iconSize||"");const{getProjectFromHash:C}=await import("../lib/project-context.js"),L=e?C():null,F=t.find("#me-project");F.html('<option value="">\u2014 none \u2014</option>'+b.map(s=>`<option value="${O(s.slug)}">${O(s.name||s.slug)}</option>`).join("")),t.find("#me-slug").val(i.slug||""),t.find("#me-name").val(i.name||""),t.find("#me-description").val(i.description||""),F.val(i.meta?.project||L||""),Domma.icons.scan(t.get(0)),t.find("#me-save").on("click",async()=>{S(u,o);const s={variant:t.find("#me-variant").val(),position:t.find("#me-position").val(),appearOnHover:!!(t.find("#me-appearOnHover").get(0)||{}).checked,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()},a={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()},d=["fontFamily","fontSize","fontWeight","letterSpacing","iconSize"],p=Object.fromEntries(Object.entries(s).filter(([c,m])=>d.includes(c)&&m)),f={slug:a.slug,name:a.name,description:a.description,...s.variant&&{variant:s.variant},...s.position&&{position:s.position},...s.appearOnHover&&{appearOnHover:!0},...Object.keys(p).length&&{style:p},items:X(o),meta:{...i.meta||{},project:a.project||null}};try{if(e)await k.menus.create(f);else if(f.slug!==i.slug){await k.menus.create(f);const c=await k.menuLocations.get();let m=!1;for(const[A,R]of Object.entries(c))R===i.slug&&(c[A]=f.slug,m=!0);m&&await k.menuLocations.save(c),await k.menus.remove(i.slug)}else await k.menus.update(i.slug,f);E.toast("Saved.",{type:"success"}),location.hash="#/menus/edit/"+encodeURIComponent(f.slug)}catch(c){E.toast(`Save failed: ${c.message||c}`,{type:"error"})}})}};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{api as i}from"../api.js";function
|
|
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,m;try{[l,r,p]=await Promise.all([i.menuLocations.registry().catch(()=>[]),i.menuLocations.get().catch(()=>({})),i.menus.list().catch(()=>[])]),m=await Promise.all(p.map(e=>i.menus.get(e.slug).catch(()=>null)))}finally{c.destroy()}const u={};for(const e of m)e&&e.slug&&(u[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",{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=u[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 o}from"../api.js";import{filterByProject as
|
|
1
|
+
import{api as o}from"../api.js";import{filterByProject as b,getProjectFromHash as y}from"../lib/project-context.js";function l(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 p=E.loader(a.get(0),{type:"dots"});let c,r;try{[c,r]=await Promise.all([o.menus.list().catch(()=>[]),o.menuLocations.get().catch(()=>({}))])}finally{p.destroy()}const d=y(),m=d?b(c,d):c,s={};for(const[e,t]of Object.entries(r||{}))t&&(s[t]||(s[t]=[]),s[t].push(e));T.create("#menus-table",{data:m,emptyMessage:'No menus yet. Click "New menu" to create one.',columns:[{key:"slug",title:"Slug",sortable:!0,render:e=>`<code>${l(e)}</code>`},{key:"name",title:"Name",sortable:!0,render:(e,t)=>`<a href="#/menus/edit/${encodeURIComponent(t.slug)}" style="font-weight:600;">${l(e||t.slug)}</a>`},{key:"itemsCount",title:"Items",render:(e,t)=>String(t.itemsCount??(Array.isArray(t.items)?t.items.length:0))},{key:"mapped",title:"Mapped",render:(e,t)=>{const n=s[t.slug]||[];return n.length?n.map(i=>`<span class="badge badge-info">${l(i)}</span>`).join(" "):'<span class="text-muted">\u2014</span>'}},{key:"bundled",title:"Bundled",render:(e,t)=>t.meta?.bundled?'<span class="badge badge-secondary">bundled</span>':""},{key:"_actions",title:"Actions",render:(e,t)=>{const n=(s[t.slug]||[]).length>0,i=n?'data-tooltip="Unmap this menu before deleting"':"",g=n?"disabled":"",u=l(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"></span></a>
|
|
3
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}" ${
|
|
5
|
-
`}}]}),Domma.icons.scan("#menus-table"),document.querySelectorAll("#menus-table [data-tooltip]").forEach(e=>{E.tooltip(e,{content:e.getAttribute("data-tooltip"),position:"top"})}),
|
|
4
|
+
<button class="btn btn-sm btn-danger btn-delete" data-slug="${u}" ${g} ${i}><span data-icon="trash"></span></button>
|
|
5
|
+
`}}]}),Domma.icons.scan("#menus-table"),document.querySelectorAll("#menus-table [data-tooltip]").forEach(e=>{E.tooltip(e,{content:e.getAttribute("data-tooltip"),position:"top"})}),a.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"})}}),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 o.menus.remove(e),E.toast("Menu deleted.",{type:"success"}),location.reload()}catch(n){E.toast(`Delete failed: ${n.message||n}`,{type:"error"})}})}};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{api as
|
|
1
|
+
import{api as s}from"../api.js";function f(o){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"}[o]||"string"}export const myProfileView={templateUrl:"/admin/js/templates/my-profile.html",async onMount(o){const i=E.loader(o.get(0),{type:"dots"});let r,c;try{[r,c]=await Promise.all([s.auth.me().catch(()=>null),s.collections.get("user-profiles").catch(()=>null)])}finally{i.destroy()}if(!r){E.toast("Could not load your account details.",{type:"error"});return}const u={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"}},password:{type:"password",required:!1,minLength:0,label:"New Password",formConfig:{placeholder:"\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022",autocomplete:"new-password",tooltip:"Leave blank to keep your current password"}}},d={name:r.name||"",email:r.email||""};F.render("#account-form-container",u,d,{layout:"stacked",submitText:"Save Account",onSubmit:async t=>{if(t.password&&t.password.length<8)return E.toast("Password must be at least 8 characters.",{type:"warning"}),!1;const a={name:t.name,email:t.email};t.password&&(a.password=t.password);try{await s.auth.updateMe(a),E.toast("Account updated.",{type:"success"})}catch(e){return E.toast(`Save failed: ${e.message||"Unknown error"}`,{type:"error"}),!1}}});const p=c?.fields||[];if(p.length>0){o.find("#profile-card").show();const t={},a={};for(const e of p){const l={type:f(e.type),label:e.label||e.name};e.required&&(l.required=!0),e.placeholder&&(l.formConfig={placeholder:e.placeholder}),e.options&&Array.isArray(e.options)&&(l.options=e.options.map(n=>typeof n=="string"?{value:n,label:n}:n)),t[e.name]=l,a[e.name]=r.profile?.[e.name]??""}F.render("#profile-form-container",t,a,{layout:"stacked",submitText:"Save Profile",onSubmit:async e=>{try{await s.auth.updateMe({profile:e}),E.toast("Profile saved.",{type:"success"})}catch(m){return E.toast(`Save failed: ${m.message||"Unknown error"}`,{type:"error"}),!1}}})}Domma.icons.scan()}};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{api as p}from"../api.js";export const notificationsView={templateUrl:"/admin/js/templates/notifications.html",async onMount(
|
|
1
|
+
import{api as p}from"../api.js";export const notificationsView={templateUrl:"/admin/js/templates/notifications.html",async onMount(o){const f=o.find("#notif-list"),C=o.find("#notif-empty"),v=E.loader(o.get(0),{type:"dots"});let c=[];try{c=await p.system.notifications.list()}catch{E.toast("Could not load notifications.",{type:"error"});return}finally{v.destroy()}if(!Array.isArray(c)||c.length===0){C.removeAttr("hidden"),Domma.icons.scan(o.get(0));return}c.forEach(e=>{e.unread&&p.system.notifications.markRead(e.id).catch(()=>{})}),c.forEach(e=>{const t=e.data||{},s=t.severity||"info",l=t.createdAt?new Date(t.createdAt).toLocaleString():"",d=s==="critical"?"danger":s==="warning"?"warning":s==="success"?"success":"info",n=document.createElement("div");n.className="card mb-3 notif-card",n.dataset.id=String(e.id);const i=document.createElement("div");i.className="card-header",i.style.cssText="display:flex;align-items:center;gap:.5rem;";const y=document.createElement("span");y.className=`badge badge-${d}`,y.textContent=s.toUpperCase();const g=document.createElement("strong");g.style.flex="1",g.textContent=t.title||"";const h=document.createElement("small");h.className="text-muted",h.textContent=l,i.appendChild(y),i.appendChild(g),i.appendChild(h);const r=document.createElement("div");r.className="card-body";const u=document.createElement("p");if(u.style.cssText="white-space:pre-wrap;margin:0 0 .75rem",u.textContent=t.body||"",r.appendChild(u),t.link){const a=document.createElement("a");a.href=t.link,a.target="_blank",a.rel="noopener",a.className="btn btn-sm btn-secondary",a.textContent="Open Link ";const x=document.createElement("span");x.dataset.icon="external-link",a.appendChild(x),r.appendChild(a)}const b=document.createElement("div");b.style.cssText="margin-top:.5rem;display:flex;gap:.5rem;";const m=document.createElement("button");m.className="btn btn-sm btn-secondary js-dismiss",m.dataset.id=String(e.id),m.textContent="Dismiss",b.appendChild(m),r.appendChild(b),n.appendChild(i),n.appendChild(r),f.get(0).appendChild(n)}),f.get(0).addEventListener("click",async e=>{const t=e.target.closest(".js-dismiss");if(!t)return;const s=t.dataset.id;try{await p.system.notifications.dismiss(s);const l=t.closest(".notif-card");l&&l.remove(),f.get(0).querySelectorAll(".notif-card").length===0&&C.removeAttr("hidden");const{count:d}=await p.system.notifications.unreadCount().catch(()=>({count:0})),n=$("#topbar-bell-badge");d>0?n.text(d>99?"99+":String(d)).removeAttr("hidden"):n.attr("hidden","").text("")}catch{E.toast("Could not dismiss notification.",{type:"error"})}}),Domma.icons.scan(o.get(0))}};
|