domma-cms 1.15.0 → 1.16.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/CHANGELOG.md +12 -0
- package/admin/css/admin.css +1 -1
- package/admin/js/lib/admin-brand.js +1 -1
- package/admin/js/lib/google-analytics-panel.js +9 -0
- package/admin/js/lib/google-business-callback.js +1 -0
- package/admin/js/lib/google-business-import.js +3 -0
- package/admin/js/lib/google-business-panel.js +32 -0
- package/admin/js/lib/seo-defaults.js +1 -0
- package/admin/js/lib/sidebar-renderer.js +5 -5
- package/admin/js/lib/site-search-section.js +1 -0
- package/admin/js/lib/site-settings-arrange.js +3 -3
- package/admin/js/lib/topbar-account.js +1 -1
- package/admin/js/lib/topbar-cache.js +6 -0
- package/admin/js/lib/topbar-notifications.js +4 -4
- package/admin/js/templates/docs/api-navigation.html +9 -3
- package/admin/js/templates/docs/usage-navigation.html +4 -0
- package/admin/js/templates/docs/usage-site-settings.html +7 -5
- package/admin/js/templates/settings.html +8 -41
- package/admin/js/views/analytics.js +8 -6
- package/admin/js/views/menu-editor.js +16 -16
- package/admin/js/views/search.js +2 -2
- package/admin/js/views/seo.js +50 -42
- package/admin/js/views/settings.js +3 -3
- package/docs/DommaFeatures.md +2 -1
- package/docs/google-business-profile.md +44 -0
- package/docs/screenshots/google-business-profile-review.png +0 -0
- package/docs/screenshots/notifications-cache.png +0 -0
- package/docs/screenshots/site-settings-search.png +0 -0
- package/package.json +1 -1
- package/public/js/site.js +1 -1
- package/server/routes/api/analytics.js +19 -0
- package/server/routes/api/google-business.js +64 -0
- package/server/routes/api/menus.js +8 -4
- package/server/routes/api/seo.js +14 -3
- package/server/services/adminBrand.js +1 -0
- package/server/services/googleBusiness.js +225 -0
- package/server/services/menus.js +27 -2
- package/server/services/renderer.js +2 -0
- package/server/templates/page.html +1 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{apiRequest as
|
|
1
|
+
import{apiRequest as h,getToken as fe,refreshAccessToken as ye}from"../api.js";import{addBannerAction as ge}from"../lib/plugin-chrome.js";import{helpPopovers as ae}from"../lib/help-popover.js";import{mountGoogleAnalytics as we}from"../lib/google-analytics-panel.js";import{onceClosed as Me,openMenuAt as se,settingsStore as ke,sidebarCount as ne,viewLifetime as Ee}from"../lib/tool-kit.js";import{DEFAULT_SETTINGS as Re,journeyRows as Se,kpis as Ce,LIMITS as Ae,normaliseSettings as oe,pageEditHash as Le,RANGE_LABEL as q,RANGES as Te,seriesRange as xe,siteAddress as Pe,tabCounts as $e,topRows as je}from"../lib/analytics-shape.js";import{fmtDate as He}from"../lib/dates.js";import{SHAPES as Ie,showLoading as Ue}from"../lib/loading.js";const De="https://cdn.jsdelivr.net/npm/chart.js@4.4.1/dist/chart.umd.min.js",Oe=3e4,le=ke("analytics.settings",oe),_e=`
|
|
2
2
|
<div class="an-row" tabindex="0" data-url="{{url}}">
|
|
3
3
|
<span class="an-bar-fill" style="width: {{bar}}"></span>
|
|
4
4
|
<span class="an-rank">{{rank}}</span>
|
|
@@ -12,18 +12,20 @@ import{apiRequest as b,getToken as be,refreshAccessToken as he}from"../api.js";i
|
|
|
12
12
|
<span class="an-url" title="{{label}}">{{label}}</span>
|
|
13
13
|
<span class="an-num">{{count}}</span>
|
|
14
14
|
<button type="button" class="an-kebab" aria-label="Page actions" title="Actions">\u22EE</button>
|
|
15
|
-
</div>`,
|
|
15
|
+
</div>`,Ne=`
|
|
16
16
|
<div class="an-row" tabindex="0" data-url="{{url}}">
|
|
17
17
|
<span class="an-url" title="{{url}}">{{url}}</span>
|
|
18
18
|
<span class="an-num" title="Views per hour today, against the usual {{baseline}}">{{hits}}/h</span>
|
|
19
19
|
<span class="an-share">\xD7{{ratio}}</span>
|
|
20
20
|
<button type="button" class="an-kebab" aria-label="Page actions" title="Actions">\u22EE</button>
|
|
21
|
-
</div>`;function
|
|
21
|
+
</div>`;function Ve(){return window.Chart?Promise.resolve():new Promise((v,n)=>{const a=document.createElement("script");a.src=De,a.async=!0,a.onload=()=>v(),a.onerror=()=>n(new Error("chart.js load failed")),document.head.appendChild(a)})}function qe(v){const n=document.createElement("template");return n.innerHTML=v.trim(),n.content.firstElementChild}async function Be(v){const n=m=>fetch("/api/analytics/export.csv?range="+encodeURIComponent(v),{headers:m?{Authorization:"Bearer "+m}:{}});let a=await n(fe());if(a.status===401&&(a=await n(await ye())),!a.ok)throw new Error("export failed");const l=await a.blob(),d=(a.headers.get("Content-Disposition")||"").match(/filename="?([^"]+)"?/),u=URL.createObjectURL(l),c=document.createElement("a");c.href=u,c.download=d?d[1]:"analytics.csv",document.body.appendChild(c),c.click(),c.remove(),URL.revokeObjectURL(u)}const o=Ee(/^#\/analytics\b/);export const analyticsView={templateUrl:"/admin/js/templates/analytics.html",async onMount(v){o.reset();const n=v.get(0);let a=!0;o.add(()=>{a=!1});const l=M.observable(le.read()),d=M.observable(l.peek().range),u=M.observable("pages"),c=M.observable(""),m=M.observable(null),G=M.observable([]),w=M.observable(null),T=M.observable([]),f=M.observable(null),k=M.observable(""),y=M.observable(""),x=M.observable(null),P=M.observable(null),$=M.observable(null),j=M.observable(!0),F=M.observable(!!window.Chart);let W=new Set;const J=M.observable([]),z=M.observable("");o.add(M.effect(()=>{const e=m.value,{rows:t,matched:s}=je(e?.top,{filter:c.value,limit:l.value.limit,total:e?.total});J.value=t,z.value=e?e.top.length?s?s>t.length?`Showing ${t.length} of ${s} pages - the cog changes how many.`:"":"No pages match that filter.":"No page views recorded in this range yet.":""}));const K=M.observable([]),Y=M.observable([]),Z=M.observable([]);o.add(M.effect(()=>{const e=Se(w.value);K.value=e.entry,Y.value=e.exit,Z.value=e.paths}));const ie={range:d,section:u,filter:c,spikes:T,pageRows:J,pagesNote:z,entryRows:K,exitRows:Y,pathRows:Z,loadError:k,liveError:y,loading:j,counts:M.computed(()=>$e(m.value,w.value,j.value?null:T.value)),freshness:M.computed(()=>x.value?`Last updated ${D(x.value).format("HH:mm:ss")} \xB7 ${q[$.value]||$.value}${k.value?" \xB7 Figures are stale":""}`:k.value?"Figures unavailable":"Loading figures\u2026"),kpi:M.computed(()=>Ce(m.value,w.value)),isAllTime:M.computed(()=>d.value==="all"),sessionsCount:M.computed(()=>w.value?.totalSessions??0),liveText:M.computed(()=>y.value?f.value===null?"Active now unavailable":`${f.value} active \xB7 stale`:f.value===null?"Active now: -":`${f.value} active now`),liveClass:M.computed(()=>!y.value&&f.value?"is-on":""),liveTitle:M.computed(()=>(P.value?`Last updated ${D(P.value).format("HH:mm:ss")}. `:"")+(y.value||(l.value.live?"Refreshes every 30 seconds":"Live refresh is off - see settings")))};o.add(M.effect(()=>le.write(l.value)));const re=(e,t)=>{l.value=oe({...l.peek(),[e]:t})};let Q=Ue(n.querySelector(".an-loading"),Ie.rows);const X=async()=>{const e=Q;Q=null,e&&await e.done(),a&&(j.value=!1)};let H=0;async function C(){const e=d.peek(),t=++H;try{const[s,i,g,S]=await Promise.all([h("/analytics/stats?range="+encodeURIComponent(e)),h("/analytics/series?range="+encodeURIComponent(xe(e))),h("/analytics/journeys?range="+encodeURIComponent(e)),h("/analytics/spikes")]);if(await X(),!a||t!==H)return;m.value=s,G.value=i?.series??[],w.value=g,T.value=(S?.items??[]).map(p=>({...p,key:p.url})),$.value=e,x.value=new Date().toISOString(),k.value=""}catch{await X(),a&&t===H&&(k.value="Could not refresh analytics. Previous figures, if shown, are retained.")}}async function ce(){try{const e=await h("/pages");a&&(W=new Set((Array.isArray(e)?e:[]).map(t=>t.urlPath).filter(Boolean)))}catch{}}let U=0;async function O(){const e=++U;try{const t=await h("/analytics/realtime");if(!a||e!==U)return;f.value=t?.activeSessions??0,P.value=new Date().toISOString(),y.value="",ne("#/analytics")}catch{a&&e===U&&(y.value="Live visitor count is unavailable. Retry to refresh it.")}}let ee=!0;o.add(M.effect(()=>{if(d.value,ee){ee=!1;return}C()}));let _=null;o.add(M.effect(()=>{clearInterval(_),_=l.value.live?setInterval(()=>{document.hidden||O()},Oe):null})),o.add(()=>clearInterval(_));const te=n.querySelector(".an-chart");let A=null;o.add(()=>{A?.destroy(),A=null}),o.add(M.effect(()=>{const e=G.value,t=l.value.chart;if(!F.value||!window.Chart||!te.isConnected)return;const s=getComputedStyle(document.documentElement),i=s.getPropertyValue("--dm-primary").trim()||"rgb(99, 102, 241)",g=s.getPropertyValue("--dm-text-muted").trim()||"#888",S=`color-mix(in srgb, ${s.getPropertyValue("--dm-border").trim()||"#888"} 45%, transparent)`;A?.destroy(),A=new window.Chart(te,{type:t,data:{labels:e.map(p=>He(p.date).slice(0,5)),datasets:[{label:"Page views",data:e.map(p=>p.hits),borderColor:i,backgroundColor:t==="bar"?i:`color-mix(in srgb, ${i} 18%, transparent)`,fill:t==="line",tension:.3,borderWidth:2,pointRadius:e.length>40?0:2,borderRadius:3}]},options:{responsive:!0,maintainAspectRatio:!1,animation:!1,plugins:{legend:{display:!1}},scales:{y:{beginAtZero:!0,ticks:{precision:0,color:g},grid:{color:S}},x:{ticks:{maxRotation:0,autoSkip:!0,maxTicksLimit:10,color:g},grid:{display:!1}}}}})}));let b=null;o.add(()=>{try{b?.close(!0)}catch{}});function de(){if(!b){b=!0;try{ue()}catch(e){throw b=null,e}}}function ue(){const e=E.slideover({title:"Analytics settings",size:"md",position:"right"}),t=qe(`
|
|
22
22
|
<div class="an-panel-form">
|
|
23
|
+
<section data-google-analytics></section>
|
|
24
|
+
<h4>Display preferences</h4>
|
|
23
25
|
<div>
|
|
24
26
|
<label class="form-label" for="an-set-range">Open on</label>
|
|
25
27
|
<select id="an-set-range" class="form-input" data-model="range.value">
|
|
26
|
-
${
|
|
28
|
+
${Te.map(r=>`<option value="${r}">${q[r]}</option>`).join("")}
|
|
27
29
|
</select>
|
|
28
30
|
</div>
|
|
29
31
|
<div>
|
|
@@ -31,7 +33,7 @@ import{apiRequest as b,getToken as be,refreshAccessToken as he}from"../api.js";i
|
|
|
31
33
|
<span data-help="How many pages the Top pages list shows. The filter still searches all of them, and the CSV export always has every page."></span>
|
|
32
34
|
</label>
|
|
33
35
|
<select id="an-set-limit" class="form-input" data-model="limit.value">
|
|
34
|
-
${
|
|
36
|
+
${Ae.map(r=>`<option value="${r}">${r||"All"}</option>`).join("")}
|
|
35
37
|
</select>
|
|
36
38
|
</div>
|
|
37
39
|
<div>
|
|
@@ -59,4 +61,4 @@ import{apiRequest as b,getToken as be,refreshAccessToken as he}from"../api.js";i
|
|
|
59
61
|
<span class="spacer"></span>
|
|
60
62
|
<button type="button" class="btn btn-sm btn-primary" data-on-click="done">Done</button>
|
|
61
63
|
</div>
|
|
62
|
-
</div>`),s=(
|
|
64
|
+
</div>`),s=(r,he=V=>V)=>M.computed({read:()=>l.value[r],write:V=>re(r,he(V))}),i={range:s("range"),limit:s("limit",Number),chart:s("chart"),live:s("live"),confirmReset:s("confirmReset")};e.element.appendChild(t);const g=ae(t),S=M.applyBindings(i,t,{methods:{done:()=>e.close(),reset:()=>{l.value={...Re}}}}),p=we(t.querySelector("[data-google-analytics]"));Me(e,()=>{p.dispose(),S.dispose(),g(),b===e&&(b=null)});const be=e.close.bind(e);e.close=async r=>{!r&&p.dirty()&&!await E.confirm("Close with unsaved Google Analytics changes? They will be kept as a draft in this browser.")||be(r)},b=e,e.open()}ge(v,{id:"analytics-settings",icon:"settings",title:"Analytics settings",onClick:de});const R=[...n.querySelectorAll(".an-list")];R[0].innerHTML=_e,R[1].innerHTML=B,R[2].innerHTML=B,R[3].innerHTML=B,R[4].innerHTML=Ne,o.add(ae(n,{position:"bottom"}));const pe=M.applyBindings(ie,n,{methods:{refresh:()=>{C(),O()},showPages:()=>{u.value="pages"},showJourneys:()=>{u.value="journeys"},showSpikes:()=>{u.value="spikes"}}});o.add(()=>pe.dispose());const L=e=>Pe(e,location.origin),N=e=>{const t=L(e);t?window.open(t,"_blank","noopener"):E.toast("That address is not on this site, so it is not opened.",{type:"warning"})},ve=E.contextMenu(n,{cursor:"glyph",match:".an-row",inherit:!1,density:"compact",items:e=>{const t=e.dataset.url,s=Le(t,W);return[{header:t},{label:"Open on the site",icon:"external-link",disabled:!L(t),action:()=>N(t)},...s?[{label:"Edit the page",icon:"edit",action:()=>{location.hash=s}}]:[],{label:"Copy address",icon:"copy",disabled:!L(t),action:async()=>{const i=L(t);if(i)try{await navigator.clipboard.writeText(i),E.toast("Address copied.",{type:"success"})}catch{E.toast(i,{type:"info",duration:8e3})}}},{label:"Show only this page",icon:"filter",disabled:u.peek()==="pages"&&c.peek()===t,action:()=>{u.value="pages",c.value=t}}]}});o.add(()=>ve?.destroy?.());const me=E.contextMenu(n.querySelector(".an-bar-end"),{cursor:"glyph",match:".an-more",inherit:!1,density:"compact",items:()=>[{label:`Export ${q[d.peek()].toLowerCase()} as CSV`,icon:"download",action:async()=>{try{await Be(d.peek())}catch{E.toast("Export failed.",{type:"error"})}}},{type:"divider"},{label:"Reset all statistics\u2026",icon:"trash",danger:!0,action:async()=>{if(!(l.peek().confirmReset&&!await E.confirm("Delete every analytics counter for this site? This cannot be undone.")))try{if(await h("/analytics/stats",{method:"DELETE"}),!a)return;E.toast("Analytics reset.",{type:"success"}),ne("#/analytics"),C()}catch{E.toast("Reset failed.",{type:"error"})}}}]});o.add(()=>me?.destroy?.()),n.addEventListener("click",e=>{const t=e.target.closest(".an-kebab, .an-more");if(t){se(t);return}const s=e.target.closest(".an-row");s?.dataset.url&&N(s.dataset.url)}),n.addEventListener("keydown",e=>{const t=e.target.closest(".an-row");t&&(e.key==="F10"&&e.shiftKey?(e.preventDefault(),se(t)):e.key==="Enter"&&e.target===t&&t.dataset.url&&N(t.dataset.url))}),I.scan(n),Ve().then(()=>{a&&(F.value=!0)},()=>{a&&E.toast("The chart library could not be loaded; figures still work.",{type:"warning"})}),ce(),await Promise.all([C(),O()])}};
|
|
@@ -1,32 +1,32 @@
|
|
|
1
|
-
import{api as
|
|
2
|
-
<div class="menu-tree-row menu-tree-row--separator" data-id="${
|
|
1
|
+
import{api as D}from"../api.js";import{colourToCss as xt,HIGHLIGHT_HOVER as dt,HIGHLIGHT_ACTIVE as ut,HIGHLIGHT_HOVER_LABELS as mt,HIGHLIGHT_ACTIVE_LABELS as pt,cssClassProblems as ht,normaliseCssClasses as At,ENTRANCE_ANIMATIONS as nt,ENTRANCE_ANIMATION_LABELS as Et}from"/public/js/menu-decor.mjs?v=20261014-menu-entrance";import{openIconPicker as $t}from"../lib/card-builder.js";import{normalisePanel as Ct,DESCRIPTION_MAX as Lt}from"/public/js/menu-look.mjs?v=20261029-bar-shapes";import{sortableMenuRows as Tt}from"../lib/menu-drag.js";import{placeTools as _t,ensureLockedTools as It,isLockedUrl as Ht,groupKeyFor as Nt,slugify as Ot,GROUP_ICON as Rt,TOOLS_FOLDER_KEY as Bt}from"../lib/sidebar-grouping.js";import{flatten as ft,nest as Dt,uid as Ve}from"../lib/menu-rows.js";import{addBannerAction as ot}from"../lib/plugin-chrome.js";import{helpPopovers as Pt}from"../lib/help-popover.js";import{onceClosed as jt,openMenuAt as at,sidebarCount as Ft}from"../lib/tool-kit.js";import{addChild as Mt,bindingFrom as zt,duplicateRow as Ut,indentRow as Wt,insertBelow as Kt,isStructural as ge,menuFromForm as qt,menuProblems as Gt,moveRow as gt,outdentRow as Vt,siblingsOf as Yt,snapshot as Ye}from"../lib/menu-editor-arrange.js";import{fmtDateTime as Xt}from"../lib/dates.js";const Jt=760,Qt="admin-sidebar",Zt=/^\/menus\/(new|edit\/[^?#]+)(\?.*)?$/;function A(n){return String(n||"").replace(/&/g,"&").replace(/"/g,""").replace(/</g,"<")}const De=n=>String(n??"").replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""");function en(n,u){let i=0,r=u.find(l=>l.id===n);for(;r&&r.parentId;)i++,r=u.find(l=>l.id===r.parentId);return i}function vt(n){const u=new Set,i=new Set,r=l=>{for(const w of n.filter(L=>L.parentId===l))!w.type&&Ht(w.url)&&!i.has(w.url)&&(i.add(w.url),u.add(w.id)),r(w.id)};return r(null),u}const Xe='<button type="button" class="btn btn-sm btn-ghost mex-kebab" aria-label="Actions" title="Actions (right-click, or Shift+F10)">\u22EE</button>';function tn(n,u,i=!1){const r=i?vt(u):new Set,l=[];function w(L){for(const m of u.filter(G=>G.parentId===L)){const C=`style="margin-left:${en(m.id,u)*28}px"`;if(m.type==="separator"){l.push(`
|
|
2
|
+
<div class="menu-tree-row menu-tree-row--separator" data-id="${m.id}" tabindex="0" ${C}>
|
|
3
3
|
<span class="menu-tree-grip" data-icon="grip-vertical" data-icon-size="18" title="Drag to move"></span>
|
|
4
4
|
<span class="me-sep-line"><span class="me-sep-label">Separator</span></span>
|
|
5
5
|
${Xe}
|
|
6
|
-
</div>`);continue}if(
|
|
7
|
-
<div class="menu-tree-row menu-tree-row--spacer" data-id="${
|
|
6
|
+
</div>`);continue}if(m.type==="spacer"){const ve=["xs","sm","md","lg","xl","flex"].map(te=>`<option value="${te}"${(m.size||"md")===te?" selected":""}>${te==="flex"?"flexible":te}</option>`).join("");l.push(`
|
|
7
|
+
<div class="menu-tree-row menu-tree-row--spacer" data-id="${m.id}" tabindex="0" ${C}>
|
|
8
8
|
<span class="menu-tree-grip" data-icon="grip-vertical" data-icon-size="18" title="Drag to move"></span>
|
|
9
9
|
<span class="me-spacer-line">
|
|
10
10
|
<span class="me-spacer-label">Spacer</span>
|
|
11
11
|
<select class="form-input me-spacer-size" title="Spacer size - flexible fills the leftover room" aria-label="Spacer size">${ve}</select>
|
|
12
12
|
</span>
|
|
13
13
|
${Xe}
|
|
14
|
-
</div>`);continue}if(
|
|
15
|
-
<div class="menu-tree-row menu-tree-row--switcher" data-id="${
|
|
14
|
+
</div>`);continue}if(m.type==="theme-switcher"){l.push(`
|
|
15
|
+
<div class="menu-tree-row menu-tree-row--switcher" data-id="${m.id}" tabindex="0" ${C}>
|
|
16
16
|
<span class="menu-tree-grip" data-icon="grip-vertical" data-icon-size="18" title="Drag to move"></span>
|
|
17
17
|
<span class="me-sep-line"><span class="me-sep-label"><span data-icon="palette"></span> Theme switcher - the themes chosen under Theme > Theme switcher</span></span>
|
|
18
18
|
${Xe}
|
|
19
|
-
</div>`);continue}const
|
|
20
|
-
<div class="menu-tree-row menu-tree-row--slim${
|
|
19
|
+
</div>`);continue}const V=m.badge&&(m.badge.text||m.badge.countFrom)?`<span class="dm-menu-badge">${A(String(m.badge.text||"#"))}</span>`:"",N=m.tool,f=N&&(N.persisted||m.touched),le=N?N.orphan?' <span class="badge badge-secondary me-tool-badge" title="A placeholder for a Tool that is not enabled - it returns here when it is">Not enabled</span>':` <span class="badge badge-secondary me-tool-badge" title="${f?"A Tool, placed here by this menu":"A Tool in its default place - move or edit it to place it here"}">${A(N.source||"Tool")}${f?"":" \xB7 default"}</span>`:"",Z=r.has(m.id),se=xt(m.colour),ee=se?` style="color:${A(se)}"`:"",ie=Z?'<button type="button" class="btn btn-sm btn-ghost" disabled title="Always shown - it can be moved, not hidden" aria-label="Always shown"><span data-icon="lock"></span></button>':`<button type="button" class="btn btn-sm btn-ghost me-hidden ${m.hidden?"active":""}" title="${m.hidden?"Hidden - click to show":"Hide"}" aria-label="${m.hidden?"Show":"Hide"}" aria-pressed="${m.hidden?"true":"false"}"><span data-icon="${m.hidden?"eye-off":"eye"}"></span></button>`;l.push(`
|
|
20
|
+
<div class="menu-tree-row menu-tree-row--slim${m.hidden?" menu-tree-row--hidden":""}${N?" menu-tree-row--tool":""}${N&&!f?" menu-tree-row--default":""}" data-id="${m.id}" tabindex="0" ${C}>
|
|
21
21
|
<span class="menu-tree-grip" data-icon="grip-vertical" data-icon-size="18" title="Drag to move"></span>
|
|
22
|
-
<span class="me-row-label${!
|
|
23
|
-
<span class="me-row-url">${A(
|
|
22
|
+
<span class="me-row-label${!m.text&&!N?" mex-untitled":""}"${ee}>${A(m.text||N?.label||"(no label - click to add one)")}${V}${le}</span>
|
|
23
|
+
<span class="me-row-url">${A(m.url||"")}</span>
|
|
24
24
|
${ie}
|
|
25
25
|
<button type="button" class="btn btn-sm btn-ghost me-edit" title="Edit" aria-label="Edit"><span data-icon="edit"></span></button>
|
|
26
26
|
${Xe}
|
|
27
|
-
</div>`),
|
|
28
|
-
`),placeholder:"One per line, up to six"})),t=
|
|
29
|
-
`),button:{text:f("featureBtnText"),url:f("featureBtnUrl")}}:null})}Se=!0,_.close(!0),l()})}let K=null,bt=!1;function an(){bt||!window.R?.use||(bt=!0,R.use((n,d,i)=>{const r=d?.path||"";return!(Qt.test(r)&&(n?.path||"")!==r)||!K?.dirty()?i():E.confirm(`You have unsaved changes to the menu "${Ye(K.name())}". Leave anyway? They are kept as a draft for your next visit.`).then(k=>{k?i():history.replaceState(null,"",`#${r}`)})}),M.subscribe("router:afterChange",({to:n}={})=>{if(K&&(n?.path||"")!==K.path){const d=K;K=null,d.dispose()}}))}function yt(n,d){const i=n.querySelector(".dm-plugin-banner-name"),r=i?.querySelector(".dm-plugin-banner-sep");if(!(!i||!r)){for(;r.nextSibling;)r.nextSibling.remove();i.appendChild(document.createTextNode(d))}}export const menuEditorView={templateUrl:"/admin/js/templates/menu-editor.html",async onMount(n){if(an(),K){const e=K;K=null,e.dispose()}const d=[],i=e=>(d.push(e),e);let r=!1;const l=n.get(0).querySelector(".mex")||n.get(0),k=()=>!r&&l.isConnected,L=location.hash.replace(/^#/,""),u=location.hash.match(/^#\/menus\/edit\/([^?#]+)/),q=u?decodeURIComponent(u[1]):null,C=!q,Q=`menu-editor.draft.${q||"new"}`,N=E.loader(n.get(0),{type:"dots"});let v,le,Z,se,ee,ie,ve;try{if(C)v={slug:"",name:"",description:"",items:[]};else try{v=await P.menus.get(q)}catch(e){E.toast(`Failed to load menu: ${e.message||e}`,{type:"error"}),location.hash="#/menus";return}[le,Z,se,ee,ie,ve]=await Promise.all([P.projects.list().catch(()=>[]),H.get("/api/auth/permissions-registry").catch(()=>({resources:[]})),P.collections.list().catch(()=>[]),P.get("/collections/roles/entries?limit=100").catch(()=>null),P.menuLocations.registry().catch(()=>[]),P.pages.list().catch(()=>[])])}finally{N.destroy()}if(!l.isConnected)return;const be=(Array.isArray(ee)?ee:ee?.entries||[]).map(e=>({name:e.data?.name,label:e.data?.label,level:e.data?.level??99})).filter(e=>e.name).sort((e,o)=>o.level-e.level),D=!C&&q===Jt;let $e=[];if(D&&($e=await H.get("/api/sidebar/registered-items").catch(()=>(E.toast("Could not load the Tools - they are not shown, and saving keeps their places as they are.",{type:"warning",duration:8e3}),[])),!l.isConnected))return;const Ce=e=>ft(D?_t(Tt(e||[],$e,{forEditor:!0}),{leaveInHiddenFolders:!0}):e);let h=Ce(v.items);const j=M.observable(!1),re=M.observable(!1),Pe=M.observable(""),Le=M.observable(0);let G="";const T=l.querySelector("#me-items-list");function ye(){en(T,h,D),Le.value=h.filter(e=>!ge(e)).length}const B=()=>{ye(),Y()},V=()=>D?vt(h):new Set,ce=e=>h.find(o=>o.id===e);function de(e){requestAnimationFrame(()=>T.querySelector(`.menu-tree-row[data-id="${CSS.escape(e)}"]`)?.focus())}function Te(e){const o=g=>h.find(s=>!s.type&&!s.url&&!s.tool&&(s.key||"").toLowerCase()===g)||h.find(s=>!s.type&&!s.url&&!s.tool&&!s.key&&(s.text||"").toLowerCase()===g);let a=o(String(e.tool.folder||"").toLowerCase())||o(Bt);const p=String(e.tool.group||"").trim();if(a&&p){const g=Ht(a.key||Nt(a.text),p),s=c=>c!==e&&!c.type&&!c.url&&!c.tool;let w=h.find(c=>s(c)&&(c.key||"").toLowerCase()===g)||h.find(c=>s(c)&&c.parentId===a.id&&(!c.key||c.key.toLowerCase()===g)&&(c.text||"").toLowerCase()===p.toLowerCase());w||([w]=ft([{text:p,key:g,icon:Rt,synthesised:!0}],a.id),h.push(w)),a=w}e.parentId=a?a.id:null,h.splice(h.indexOf(e),1),h.push(e),Object.assign(e,{text:e.tool.defaults.text,icon:e.tool.defaults.icon,visibility:"",hidden:!1,badge:null,pill:null,colour:"",highlight:null,touched:!1}),e.tool={...e.tool,persisted:!1}}const _e=e=>!V().has(e.id)&&!(e.tool&&!e.tool.orphan);async function je(e){const o=ce(e);if(!o||!_e(o))return;const a=c=>{let X=c;for(;X;){if(X.id===e)return!0;X=h.find(z=>z.id===X.parentId)}return!1},p=h.filter(c=>c.id!==e&&a(c));if(p.length&&!await E.confirm(`Remove "${Ye(o.text||o.type||"this item")}" and the ${p.length} item${p.length===1?"":"s"} beneath it?`))return;const g=V(),s=p.filter(c=>c.tool),w=p.filter(c=>!c.tool&&g.has(c.id));h=h.filter(c=>!a(c)||s.includes(c)||w.includes(c));for(const c of w)c.parentId=o.parentId??null;for(const c of s)c.tool.orphan?c.parentId=o.parentId??null:Te(c);B()}function ue(e){const o=ce(e);!o||ge(o)||V().has(e)||(o.hidden=!o.hidden,o.touched=!0,B())}function x(e){const o=ce(e);!o||ge(o)||(o.locked=D&&V().has(o.id),on(o,Z.resources||[],se,be,()=>{B(),de(e)},{cards:!D}))}function O(e){const o={id:Ge(),parentId:null,...e==="separator"||e==="theme-switcher"?{type:e}:e==="spacer"?{type:e,size:"md"}:{text:"",url:"/",icon:"",visibility:"",permission:"",hidden:!1,bundled:!1}};h.push(o),B(),ge(o)?de(o.id):x(o.id)}const ke=e=>o=>{e(o)&&(B(),de(o))},Fe=ke(e=>gt(h,e,-1)),Me=ke(e=>gt(h,e,1)),Ie=ke(e=>Ut(h,e)),ze=ke(e=>Gt(h,e));function Je(e){const o=ce(e);if(!o)return[];const a=ge(o),p=V().has(e),g=Vt(h,e),s=g.findIndex(z=>z.id===e),w=o.tool,c=w&&(w.persisted||o.touched),X=z=>()=>{const Oe=Wt(h,e,z,Ge());B(),Oe&&!ge(Oe)?x(Oe.id):Oe&&de(Oe.id)};return[{header:a?{separator:"Separator",spacer:"Spacer","theme-switcher":"Theme switcher"}[o.type]:o.text||w?.label||"(untitled)"},...a?[]:[{label:"Edit\u2026",icon:"edit",action:()=>x(e)}],{type:"divider"},{label:"Add item below",icon:"plus",action:X("item")},{label:"Add sub-item",icon:"corner-down-right",disabled:a,action:()=>{const z=Ft(h,e,Ge());B(),z&&x(z.id)}},{label:"Add separator below",icon:"minus",action:X("separator")},{label:"Add spacer below",icon:"move",action:X("spacer")},...D?[]:[{label:"Add theme switcher below",icon:"palette",action:X("theme-switcher")}],{type:"divider"},{label:"Move up",icon:"arrow-up",shortcut:"Alt+\u2191",disabled:s<=0,action:()=>Fe(e)},{label:"Move down",icon:"arrow-down",shortcut:"Alt+\u2193",disabled:s<0||s>=g.length-1,action:()=>Me(e)},{label:"Indent",icon:"chevron-right",shortcut:"Alt+\u2192",disabled:s<=0||ge(g[s-1]),action:()=>Ie(e)},{label:"Outdent",icon:"chevron-left",shortcut:"Alt+\u2190",disabled:o.parentId==null,action:()=>ze(e)},{type:"divider"},...a?[]:[{label:o.hidden?"Show":"Hide",icon:o.hidden?"eye":"eye-off",disabled:p,action:()=>ue(e)}],{label:"Duplicate",icon:"copy",disabled:!!w,action:()=>{const z=zt(h,e,Ge);B(),z&&de(z.id)}},...w&&!w.orphan&&c?[{label:"Back to its default place",icon:"rotate-ccw",action:()=>{Te(o),B()}}]:[],{label:p?"Remove - always in the sidebar":w&&!w.orphan?"Remove - a Tool is hidden, not removed":"Remove\u2026",icon:"trash",danger:!0,disabled:!_e(o),action:()=>je(e)}]}const me=E.contextMenu(T,{cursor:"glyph",match:".menu-tree-row",inherit:!1,density:"compact",items:e=>Je(e.dataset.id)});i(()=>me?.destroy?.());const Qe=Lt(T,()=>h,B,{items:".menu-tree-row",handle:".menu-tree-grip"});i(()=>Qe?.destroy());const Ue=e=>{const o=e.target.closest(".menu-tree-row");if(!o)return;const a=o.dataset.id;if(e.target.closest(".mex-kebab")){at(e.target.closest(".mex-kebab"));return}if(e.target.closest(".me-hidden")){ue(a);return}if(e.target.closest(".me-edit")){x(a);return}e.target.closest("select, .menu-tree-grip, button")||x(a)},We=e=>{const o=e.target.closest(".menu-tree-row");if(!o||e.target!==o){o&&e.key==="F10"&&e.shiftKey&&(e.preventDefault(),at(o));return}const a=o.dataset.id;if(e.key==="F10"&&e.shiftKey)e.preventDefault(),at(o);else if(e.key==="Enter")e.preventDefault(),x(a);else if(e.key==="Delete")e.preventDefault(),je(a);else if(e.altKey&&e.key==="ArrowUp")e.preventDefault(),Fe(a);else if(e.altKey&&e.key==="ArrowDown")e.preventDefault(),Me(a);else if(e.altKey&&e.key==="ArrowRight")e.preventDefault(),Ie(a);else if(e.altKey&&e.key==="ArrowLeft")e.preventDefault(),ze(a);else if(e.key==="ArrowDown"||e.key==="ArrowUp"){const p=[...T.querySelectorAll(".menu-tree-row")],g=p.indexOf(o)+(e.key==="ArrowDown"?1:-1);p[g]&&(e.preventDefault(),p[g].focus())}},Ke=e=>{if(!e.target.matches(".me-spacer-size"))return;const o=ce(e.target.closest(".menu-tree-row")?.dataset.id);o&&(o.size=e.target.value,Y())};T.addEventListener("click",Ue),T.addEventListener("keydown",We),T.addEventListener("change",Ke),i(()=>{T.removeEventListener("click",Ue),T.removeEventListener("keydown",We),T.removeEventListener("change",Ke)});const t=e=>n.find(e),we=t("#me-float-accent"),pe=t("#me-float-accent-hex"),He=()=>{pe.get(0).style.display=we.val()==="__custom__"?"":"none"},Ne=()=>we.val()==="__custom__"?pe.val():we.val(),U=t("#me-highlight-accent"),_=t("#me-highlight-accent-hex"),Re=()=>{_.get(0).style.display=U.val()==="__custom__"?"":"none"},Ze=()=>U.val()==="__custom__"?_.val():U.val(),Se=()=>{const e=!!(t("#me-entrance-enabled").get(0)||{}).checked;t(".dm-entrance-field").each(function(){this.style.display=e?"":"none"});const o=t("#me-position").val()==="floating";t(".dm-float-field").each(function(){this.style.display=o?"":"none"}),o&&He();const a=t("#me-orientation").val()==="vertical";t(".dm-vertical-field").each(function(){this.style.display=a?"":"none"}),t(".dm-dock-rail-field").each(function(){this.style.display=a&&!o?"":"none"}),t(".dm-dock-bar-field").each(function(){this.style.display=!a&&!o?"":"none"});const p=!!(t("#me-dock-collapsible").get(0)||{}).checked,g=!!(t("#me-dock-autohide").get(0)||{}).checked;t(".dm-dock-collapsed-field").each(function(){this.style.display=p&&!g?"":"none"});const s=!!(t("#me-float-collapsible").get(0)||{}).checked;t(".dm-float-collapsed-field").each(function(){this.style.display=o&&s?"":"none"});const w=!a&&!D;t(".dm-look-field").each(function(){this.style.display=w?"":"none"});const c=w&&t("#me-look-dropdown").val()==="cards";t(".dm-look-cards-field").each(function(){this.style.display=c?"":"none"})},xe=(e,o,a,p)=>{const g=t(e).get(0);g&&(g.innerHTML="",g.add(new Option(p,"")),o.forEach(s=>g.add(new Option(a[s]||s,s))))};xe("#me-highlight-hover",dt,mt,"- theme default -"),xe("#me-highlight-active",ut,pt,"- theme default -");{const e=t("#me-entrance-animation").get(0);e&&(e.innerHTML="",nt.forEach(o=>e.add(new Option(At[o]||o,o))))}const Be=t("#me-bind-slot"),Ae=t("#me-bind-projects"),f=t("#me-bind-pages");Be.html('<option value="">- not bound -</option>'+(ie||[]).map(e=>`<option value="${A(e.slot)}">${A(e.label||e.slot)} (${A(e.slot)})</option>`).join("")),Ae.html(le.map(e=>`<option value="${A(e.slug)}">${A(e.name||e.slug)}</option>`).join("")),t("#me-bind-page-pick").html('<option value="">- pick a page -</option>'+ve.map(e=>{const o=e.urlPath||e.url||"";return o?`<option value="${A(o)}">${A(e.title||o)} - ${A(o)}</option>`:""}).join(""));const m=()=>Mt({slot:Be.val(),projects:Array.from(Ae.get(0).selectedOptions).map(e=>e.value),pagesText:f.val()});function F(){const e=m(),o=t("#me-bind-summary");if(!e){o.html('<span class="text-muted">Not bound - this menu renders only where it is mapped or placed by a shortcode.</span>');return}const a=[];e.projects.length&&a.push(`pages in ${e.projects.map(s=>`<code>${A(s)}</code>`).join(", ")}`),e.pages.length&&a.push(`pages matching ${e.pages.map(s=>`<code>${A(s)}</code>`).join(", ")}`);const p=e.pages.filter(s=>!/^\/[^\s?#*]*(\/\*)?$|^\/\*$/.test(s)),g=p.length?`<div class="text-danger" style="margin-top:.4rem">Not valid page patterns: ${p.map(s=>`<code>${A(s)}</code>`).join(", ")}</div>`:"";o.html(a.length?`Takes over the <code>${A(e.slot)}</code> slot on ${a.join(" and ")}.${g}`:`<span class="text-warning">Slot <code>${A(e.slot)}</code> selected but no project or page chosen - the binding will never match.</span>`)}const{getProjectFromHash:he}=await import("../lib/project-context.js");if(!k())return;const Ee=C?he():null;t("#me-project").html('<option value="">- none -</option>'+le.map(e=>`<option value="${A(e.slug)}">${A(e.name||e.slug)}</option>`).join(""));function fe(e){h=Ce(e.items),t("#me-variant").val(e.variant||""),t("#me-position").val(e.position||"");const o=t("#me-appearOnHover").get(0);o&&(o.checked=e.appearOnHover===!0),t("#me-float-anchor").val(e.float?.anchor||"TL"),t("#me-float-offsetX").val(e.float?.offsetX??""),t("#me-float-offsetY").val(e.float?.offsetY??""),t("#me-float-width").val(e.float?.width??""),t("#me-float-radius").val(e.float?.radius??""),t("#me-float-shadow").val(e.float?.shadow||""),t("#me-float-opacity").val(e.float?.opacity??""),t("#me-float-title").val(e.float?.title||"");for(const[s,w]of[["#me-float-collapsible",e.float?.collapsible],["#me-float-collapsed",e.float?.collapsed],["#me-float-movable",e.float?.movable],["#me-float-resizable",e.float?.resizable]]){const c=t(s).get(0);c&&(c.checked=w===!0)}const a=e.float?.accent||"";we.val(a.startsWith("#")?"__custom__":a),a.startsWith("#")&&pe.val(a),He(),t("#me-orientation").val(e.orientation||""),t("#me-align").val(e.align||""),t("#me-mobile-hamburger").get(0).checked=e.mobile?.hamburgerOnly===!0,t("#me-mobile-brand").get(0).checked=e.mobile?.hideBrand===!0,t("#me-look-bar").val(e.look?.bar||""),t("#me-look-brand").val(e.look?.brand||""),t("#me-look-strip").val(e.look?.strip||""),t("#me-look-dropdown").val(e.look?.dropdown||""),t("#me-look-tiles").val(e.look?.tiles||""),t("#me-look-morph").get(0).checked=e.look?.morph===!0,t("#me-look-shrink").get(0).checked=e.look?.shrink===!0,t("#me-side").val(e.side||"left"),t("#me-dock-width").val(e.dock?.width??"");for(const s of["collapsible","collapsed","autohide","hideOnScroll"]){const w=t(`#me-dock-${s}`).get(0);w&&(w.checked=e.dock?.[s]===!0)}t("#me-class").val(e.class||""),t("#me-highlight-hover").val(e.highlight?.hover||""),t("#me-highlight-active").val(e.highlight?.active||"");const p=e.highlight?.accent||"";U.val(p.startsWith("#")?"__custom__":p),p.startsWith("#")&&_.val(p),Re();{const s=t("#me-entrance-enabled").get(0);s&&(s.checked=e.entrance?.enabled===!0)}t("#me-entrance-animation").val(nt.includes(e.entrance?.animation)?e.entrance.animation:nt[0]),t("#me-entrance-stagger").val(e.entrance?.stagger??""),t("#me-entrance-duration").val(e.entrance?.duration??""),t("#me-fontFamily").val(e.style?.fontFamily||""),t("#me-fontSize").val(e.style?.fontSize||""),t("#me-fontWeight").val(e.style?.fontWeight||""),t("#me-letterSpacing").val(e.style?.letterSpacing||""),t("#me-iconSize").val(e.style?.iconSize||""),Be.val(e.binding?.slot||"");const g=new Set(e.binding?.projects||[]);Array.from(Ae.get(0).options).forEach(s=>{s.selected=g.has(s.value)}),f.val((e.binding?.pages||[]).join(`
|
|
30
|
-
`)),t("#me-slug").val(e.slug||""),t("#me-name").val(e.name||""),t("#me-description").val(e.description||""),t("#me-project").val(e.meta?.project||Ee||""),Se(),F(),ye()}const $=e=>!!(t(e).get(0)||{}).checked;function ne(){const e={variant:t("#me-variant").val(),position:t("#me-position").val(),appearOnHover:$("#me-appearOnHover"),floatAnchor:t("#me-float-anchor").val(),floatOffsetX:t("#me-float-offsetX").val(),floatOffsetY:t("#me-float-offsetY").val(),floatWidth:t("#me-float-width").val(),floatRadius:t("#me-float-radius").val(),floatShadow:t("#me-float-shadow").val(),floatAccent:Ne(),floatOpacity:t("#me-float-opacity").val(),floatTitle:t("#me-float-title").val(),floatCollapsible:$("#me-float-collapsible"),floatCollapsed:$("#me-float-collapsed"),floatMovable:$("#me-float-movable"),floatResizable:$("#me-float-resizable"),highlightHover:t("#me-highlight-hover").val(),highlightActive:t("#me-highlight-active").val(),highlightAccent:Ze(),class:t("#me-class").val(),entranceEnabled:$("#me-entrance-enabled"),entranceAnimation:t("#me-entrance-animation").val(),entranceStagger:t("#me-entrance-stagger").val(),entranceDuration:t("#me-entrance-duration").val(),orientation:t("#me-orientation").val(),align:t("#me-align").val(),side:t("#me-side").val(),fontFamily:t("#me-fontFamily").val(),fontSize:t("#me-fontSize").val(),fontWeight:t("#me-fontWeight").val(),letterSpacing:t("#me-letterSpacing").val(),iconSize:t("#me-iconSize").val(),dockWidth:t("#me-dock-width").val(),dockCollapsible:$("#me-dock-collapsible"),dockCollapsed:$("#me-dock-collapsed"),dockAutohide:$("#me-dock-autohide"),mobileHamburgerOnly:$("#me-mobile-hamburger"),mobileHideBrand:$("#me-mobile-brand"),lookBar:t("#me-look-bar").val(),lookDropdown:t("#me-look-dropdown").val(),lookBrand:t("#me-look-brand").val(),lookStrip:t("#me-look-strip").val(),lookTiles:t("#me-look-tiles").val(),lookMorph:$("#me-look-morph"),lookShrink:$("#me-look-shrink"),dockHideOnScroll:$("#me-dock-hideOnScroll")},o={slug:t("#me-slug").val(),name:t("#me-name").val(),description:t("#me-description").val(),project:t("#me-project").val()};return
|
|
31
|
-
`).map(a=>a.trim()).filter(Boolean);o.includes(e)||o.push(e),
|
|
32
|
-
`)),F(),
|
|
27
|
+
</div>`),w(m.id)}}w(null),n.innerHTML=l.join("")||'<p class="mex-note">No items yet - Add item, or right-click an item for more.</p>',I.scan(n)}function b(n,u={},...i){const r=document.createElement(n);Object.assign(r,u);for(const l of i)l!=null&&r.append(l);return r}function k(n,u,i){const r=b("label",{className:"form-field"},b("span",{className:"form-label",textContent:n}),u);return i&&r.append(b("small",{className:"form-hint",textContent:i,style:"display:block;font-size:.78em;opacity:.7;margin-top:.25rem;line-height:1.35"})),r}function nn(n,u){const i=[["","- Everyone -"],["public","Public - anyone, incl. logged-out"],...(u||[]).map(l=>[l.name,`${l.label||l.name} and above`]),...(u||[]).filter(l=>l.level!==0).map(l=>[`=${l.name}`,`${l.label||l.name} only`]),["private","Private - super-admin only"]];n&&!i.some(([l])=>l===n)&&i.push([n,`${n} (custom)`]);const r=K(i,n||"");return r.name="visibility",r}function K(n,u){const i=b("select",{className:"form-select"});return n.forEach(([r,l])=>i.add(new Option(l,r))),i.value=u??"",i}function Pe(n,u){const i=typeof u=="string"&&u.startsWith("#"),r=K([["","- none -"],["primary","primary"],["success","success"],["danger","danger"],["warning","warning"],["info","info"],["neutral","neutral"],["__custom__","Custom\u2026"]],i?"__custom__":u||""),l=b("input",{type:"color",className:"form-input me-colour-hex",value:i?u:"#000000"});l.style.display=i?"":"none",r.addEventListener("change",()=>{l.style.display=r.value==="__custom__"?"":"none"});const w=k(n,r);return w.append(l),w._read=()=>r.value==="__custom__"?l.value:r.value,w}function on(n,u){const i=b("input",{className:"form-input me-icon",name:"icon",value:u||""}),r=b("span",{className:"me-icon-preview"});u&&r.setAttribute("data-icon",u);const l=b("button",{type:"button",className:"btn btn-sm btn-ghost",textContent:"Browse\u2026"}),w=L=>{L?r.setAttribute("data-icon",L):r.removeAttribute("data-icon"),Domma.icons.scan(r.parentNode||r)};return l.addEventListener("click",()=>$t(L=>{i.value=L,w(L)})),i.addEventListener("input",()=>w(i.value.trim())),k(n,b("div",{className:"me-icon-field"},i,l,r))}function Q(...n){return b("div",{className:"me-field-grid"},...n)}async function an(n,u,i,r,l,{cards:w=!0}={}){const L=n.text||n.tool?.label||"",m=p=>b("h4",{className:"me-form-section",textContent:p}),G=k("Label",b("input",{className:"form-input",name:"text",value:n.text||""}),"The text shown for this item."),C=b("input",{className:"form-input",name:"url",value:n.url||""}),V=k("URL",C,n.tool||n.locked?"A Tool's address is its identity, so it cannot be changed here.":"Where it links. Must start with /, #, https://, or mailto: . Leave blank for a parent that only groups sub-items.");(n.tool||n.locked)&&(C.readOnly=!0);const N=on("Icon",n.icon||""),f=nn(n.visibility||"",r),le=k("Visibility",f,n.locked?"Always in the sidebar for everyone who may use it, so it cannot be restricted here.":'Who can see this item. "Role and above" shows it to that role and anyone more senior; "Role only" shows it to holders of that role alone (and the super-admin). Blank = everyone.');n.locked&&(f.disabled=!0);const Z=K([["","- no permission gate -"],...(u||[]).map(p=>[p.key,p.label||p.key])],n.permission||"");Z.name="permission";const se=k("Permission",Z,n.tool||n.locked?"A Tool is guarded by its own permission, which always applies.":"Hide unless the viewer holds this specific permission. Independent of Visibility - both must pass.");(n.tool||n.locked)&&(Z.disabled=!0);const ee=k("Badge text",b("input",{className:"form-input",name:"badgeText",value:n.badge?.text||""})),ie=K([["","- none -"],...(i||[]).map(p=>[p.slug,p.title||p.slug])],n.badge?.countFrom||"");ie.name="badgeCount";const ve=k("Badge count from",ie,"Show a live count of this collection\u2019s entries as the badge."),te=Pe("Badge colour",n.badge?.variant||""),be=K([["link","Link"],["pill","Pill"]],n.pill?"pill":"link");be.name="renderAs";const P=k("Render as",be,"Pill draws the item as a rounded chip. Pill styling applies to top-level navbar items."),$e=k("Pill style",K([["filled","Filled"],["outline","Outline"]],n.pill?.style||"filled"));$e.querySelector("select").name="pillStyle";const Ce=Pe("Pill colour",n.pill?.variant||""),h=b("div",{className:"me-pill-wrap"},Q($e,Ce));h.style.display=n.pill?"":"none",be.addEventListener("change",()=>{h.style.display=be.value==="pill"?"":"none"});const j=Pe("Text colour",n.colour||""),re=K([["","- use the menu default -"],...dt.map(p=>[p,mt[p]||p])],n.highlight?.hover||"");re.name="hlHover";const je=k("Hover effect",re,"Overrides the menu-wide hover effect for this item only."),Le=K([["","- use the menu default -"],...ut.map(p=>[p,pt[p]||p])],n.highlight?.active||"");Le.name="hlActive";const U=k("When selected",Le,"How this item is marked when it points at the page being viewed."),T=Pe("Highlight colour",n.highlight?.accent||""),ye=b("input",{className:"form-input",name:"class",value:n.class||"",placeholder:"nav-cta push-right",autocomplete:"off",spellcheck:!1}),O=k("CSS classes",ye,"Your own class names, separated by spaces, added to this item's <li> - target the link with .your-class > a. Style them in Custom CSS."),Y=b("small",{className:"text-danger",style:"display:none;margin-top:.25rem"});O.append(Y);const ce=b("textarea",{className:"form-input",name:"description",rows:2,maxLength:Lt,value:n.description||"",placeholder:"Answers every new lead in under a minute."}),de=k("Description",ce,"One short line under the label, shown when the menu's dropdowns are Cards."),Te=Pe("Tile colour",n.tile||""),_e=b("input",{type:"checkbox",name:"chip",checked:!!n.chip,style:"width:auto;margin:0"}),Fe=b("label",{className:"form-field",style:"display:flex;gap:.5rem;align-items:center;font-weight:400"},_e,b("span",{textContent:"Show as a small chip in the card's footer strip"})),ue=n.parentId==null,x=n.panel||{},B=(p,F,he,Ee=80)=>b("input",{className:"form-input",name:p,value:F||"",placeholder:he,maxLength:Ee}),ke=k("Card heading",B("panelHeading",x.heading,"The crew - six agents, one job each"),"Small capitals over the entries."),Me=k("Link text",B("panelLinkText",x.link?.text,"Meet the crew",60)),ze=k("Link URL",B("panelLinkUrl",x.link?.url,"/crew",500)),Ie=K([["","Automatic"],["1","1"],["2","2"],["3","3"],["4","4"]],x.columns?String(x.columns):"");Ie.name="panelColumns";const Ue=k("Columns",Ie),Je=k("Chip strip label",B("panelChipsLabel",x.chipsLabel,"Add to your crew",60),'Words before the chips - tick "Show as a small chip" on the sub-items that belong there.'),me=K([["","- no side box -"],["dark","Dark"],["light","Light"],["accent","Theme colour"]],x.feature?x.feature.tone||"dark":"");me.name="featureTone";const Qe=k("Side box",me,"A box down the right of the card - a call to action or a short list."),We=k("Box small heading",B("featureEyebrow",x.feature?.eyebrow,"Free - 20 minutes",60)),Ke=k("Box title",B("featureTitle",x.feature?.title,"See it live")),qe=k("Box points",b("textarea",{className:"form-input",name:"featurePoints",rows:3,value:(x.feature?.points||[]).join(`
|
|
28
|
+
`),placeholder:"One per line, up to six"})),t=k("Button text",B("featureBtnText",x.feature?.button?.text,"Book a demo",60)),we=k("Button URL",B("featureBtnUrl",x.feature?.button?.url,"/book",500)),pe=b("div",{},Q(We,Ke),qe,Q(t,we));pe.style.display=me.value?"":"none",me.addEventListener("change",()=>{pe.style.display=me.value?"":"none"});const He=w?[m("Card dropdown"),b("small",{className:"form-hint",style:"display:block;opacity:.7;margin-bottom:.4rem",textContent:"Used when the menu's Dropdown style is Cards (Behaviour tab)."}),de,Q(Te,ue?null:Fe),...ue?[ke,Q(Me,ze),Q(Ue,Je),Qe,pe]:[]]:[],Ne=b("button",{className:"btn btn-primary",textContent:"Apply",title:"Apply to the item (Ctrl+Enter) - Save the menu to keep it"}),W=b("div",{className:"me-item-form"},m("Link"),G,V,N,m("Access"),Q(le,se),m("Badge"),Q(ee,te),ve,m("Appearance"),P,h,j,m("Highlighting"),Q(je,U),T,...He,m("Styling"),O,Ne),_=E.slideover({title:L?`Edit ${De(L)}`:"Edit menu item",size:"lg",position:"right"});_.element.appendChild(W);const Oe=()=>JSON.stringify([...W.querySelectorAll("input, select, textarea")].map(p=>p.type==="checkbox"?p.checked:p.value)),Ze=Oe();let Se=!1,xe=!1;jt(_);const Re=_.close.bind(_);_.close=async p=>{if(!Se&&p!==!0&&Oe()!==Ze){if(xe)return;xe=!0;const F=await E.confirm("Discard the changes to this item?");if(xe=!1,!F)return}return Se=!0,Re()},W.addEventListener("keydown",p=>{(p.ctrlKey||p.metaKey)&&p.key==="Enter"&&(p.preventDefault(),Ne.click())}),_.open();const Ae=_.element.closest(".dm-slideover")||_.element.querySelector(".dm-slideover")||_.element;Ae.style.setProperty("width",Jt+"px","important"),Ae.style.setProperty("max-width","95vw","important"),Domma.icons.scan(W);const g=p=>W.querySelector(`[name="${p}"]`)?.value||"";Ne.addEventListener("click",()=>{const p=ht(g("class"));if(p.length){Y.textContent=p.join("; "),Y.style.display="block",ye.focus();return}n.class=At(g("class")),n.text=g("text"),n.icon=g("icon"),n.locked||(n.visibility=g("visibility")),!n.tool&&!n.locked?(n.url=g("url"),n.permission=g("permission")):n.touched=!0;const F=g("badgeText"),he=g("badgeCount"),Ee=te._read();if(n.badge=F||he?{...F&&{text:F},...he&&{countFrom:he},...Ee&&{variant:Ee}}:null,g("renderAs")==="pill"){const oe=Ce._read();n.pill={style:g("pillStyle")||"filled",...oe&&{variant:oe}}}else n.pill=null;n.colour=j._read();const fe=g("hlHover"),$=g("hlActive"),ne=T._read();if(n.highlight=fe||$||ne?{...fe&&{hover:fe},...$&&{active:$},...ne&&{accent:ne}}:null,w&&(n.description=g("description").trim(),n.tile=Te._read(),n.chip=!ue&&_e.checked,ue)){const oe=g("featureTone");n.panel=Ct({heading:g("panelHeading"),link:{text:g("panelLinkText"),url:g("panelLinkUrl")},columns:g("panelColumns"),chipsLabel:g("panelChipsLabel"),feature:oe?{tone:oe,eyebrow:g("featureEyebrow"),title:g("featureTitle"),points:g("featurePoints").split(`
|
|
29
|
+
`),button:{text:g("featureBtnText"),url:g("featureBtnUrl")}}:null})}Se=!0,_.close(!0),l()})}let q=null,bt=!1;function ln(){bt||!window.R?.use||(bt=!0,R.use((n,u,i)=>{const r=u?.path||"";return!(Zt.test(r)&&(n?.path||"")!==r)||!q?.dirty()?i():E.confirm(`You have unsaved changes to the menu "${De(q.name())}". Leave anyway? They are kept as a draft for your next visit.`).then(w=>{w?i():history.replaceState(null,"",`#${r}`)})}),M.subscribe("router:afterChange",({to:n}={})=>{if(q&&(n?.path||"")!==q.path){const u=q;q=null,u.dispose()}}))}function yt(n,u){const i=n.querySelector(".dm-plugin-banner-name"),r=i?.querySelector(".dm-plugin-banner-sep");if(!(!i||!r)){for(;r.nextSibling;)r.nextSibling.remove();i.appendChild(document.createTextNode(u))}}export const menuEditorView={templateUrl:"/admin/js/templates/menu-editor.html",async onMount(n){if(ln(),q){const e=q;q=null,e.dispose()}const u=[],i=e=>(u.push(e),e);let r=!1;const l=n.get(0).querySelector(".mex")||n.get(0),w=()=>!r&&l.isConnected,L=location.hash.replace(/^#/,""),m=location.hash.match(/^#\/menus\/edit\/([^?#]+)/),G=m?decodeURIComponent(m[1]):null,C=!G,V=`menu-editor.draft.${G||"new"}`,N=E.loader(n.get(0),{type:"dots"});let f,le,Z,se,ee,ie,ve;try{if(C)f={slug:"",name:"",description:"",items:[]};else try{f=await D.menus.get(G)}catch(e){E.toast(`Failed to load menu: ${e.message||e}`,{type:"error"}),location.hash="#/menus";return}[le,Z,se,ee,ie,ve]=await Promise.all([D.projects.list().catch(()=>[]),H.get("/api/auth/permissions-registry").catch(()=>({resources:[]})),D.collections.list().catch(()=>[]),D.get("/collections/roles/entries?limit=100").catch(()=>null),D.menuLocations.registry().catch(()=>[]),D.pages.list().catch(()=>[])])}finally{N.destroy()}if(!l.isConnected)return;const be=(Array.isArray(ee)?ee:ee?.entries||[]).map(e=>({name:e.data?.name,label:e.data?.label,level:e.data?.level??99})).filter(e=>e.name).sort((e,o)=>o.level-e.level),P=!C&&G===Qt;let $e=[];if(P&&($e=await H.get("/api/sidebar/registered-items").catch(()=>(E.toast("Could not load the Tools - they are not shown, and saving keeps their places as they are.",{type:"warning",duration:8e3}),[])),!l.isConnected))return;const Ce=e=>ft(P?It(_t(e||[],$e,{forEditor:!0}),{leaveInHiddenFolders:!0}):e);let h=Ce(f.items);const j=M.observable(!1),re=M.observable(!1),je=M.observable(""),Le=M.observable(0);let U="";const T=l.querySelector("#me-items-list");function ye(){tn(T,h,P),Le.value=h.filter(e=>!ge(e)).length}const O=()=>{ye(),X()},Y=()=>P?vt(h):new Set,ce=e=>h.find(o=>o.id===e);function de(e){requestAnimationFrame(()=>T.querySelector(`.menu-tree-row[data-id="${CSS.escape(e)}"]`)?.focus())}function Te(e){const o=v=>h.find(s=>!s.type&&!s.url&&!s.tool&&(s.key||"").toLowerCase()===v)||h.find(s=>!s.type&&!s.url&&!s.tool&&!s.key&&(s.text||"").toLowerCase()===v);let a=o(String(e.tool.folder||"").toLowerCase())||o(Bt);const c=String(e.tool.group||"").trim();if(a&&c){const v=Nt(a.key||Ot(a.text),c),s=d=>d!==e&&!d.type&&!d.url&&!d.tool;let y=h.find(d=>s(d)&&(d.key||"").toLowerCase()===v)||h.find(d=>s(d)&&d.parentId===a.id&&(!d.key||d.key.toLowerCase()===v)&&(d.text||"").toLowerCase()===c.toLowerCase());y||([y]=ft([{text:c,key:v,icon:Rt,synthesised:!0}],a.id),h.push(y)),a=y}e.parentId=a?a.id:null,h.splice(h.indexOf(e),1),h.push(e),Object.assign(e,{text:e.tool.defaults.text,icon:e.tool.defaults.icon,visibility:"",hidden:!1,badge:null,pill:null,colour:"",highlight:null,touched:!1}),e.tool={...e.tool,persisted:!1}}const _e=e=>!Y().has(e.id)&&!(e.tool&&!e.tool.orphan);async function Fe(e){const o=ce(e);if(!o||!_e(o))return;const a=d=>{let J=d;for(;J;){if(J.id===e)return!0;J=h.find(z=>z.id===J.parentId)}return!1},c=h.filter(d=>d.id!==e&&a(d));if(c.length&&!await E.confirm(`Remove "${De(o.text||o.type||"this item")}" and the ${c.length} item${c.length===1?"":"s"} beneath it?`))return;const v=Y(),s=c.filter(d=>d.tool),y=c.filter(d=>!d.tool&&v.has(d.id));h=h.filter(d=>!a(d)||s.includes(d)||y.includes(d));for(const d of y)d.parentId=o.parentId??null;for(const d of s)d.tool.orphan?d.parentId=o.parentId??null:Te(d);O()}function ue(e){const o=ce(e);!o||ge(o)||Y().has(e)||(o.hidden=!o.hidden,o.touched=!0,O())}function x(e){const o=ce(e);!o||ge(o)||(o.locked=P&&Y().has(o.id),an(o,Z.resources||[],se,be,()=>{O(),de(e)},{cards:!P}))}function B(e){const o={id:Ve(),parentId:null,...e==="separator"||e==="theme-switcher"?{type:e}:e==="spacer"?{type:e,size:"md"}:{text:"",url:"/",icon:"",visibility:"",permission:"",hidden:!1,bundled:!1}};h.push(o),O(),ge(o)?de(o.id):x(o.id)}const ke=e=>o=>{e(o)&&(O(),de(o))},Me=ke(e=>gt(h,e,-1)),ze=ke(e=>gt(h,e,1)),Ie=ke(e=>Wt(h,e)),Ue=ke(e=>Vt(h,e));function Je(e){const o=ce(e);if(!o)return[];const a=ge(o),c=Y().has(e),v=Yt(h,e),s=v.findIndex(z=>z.id===e),y=o.tool,d=y&&(y.persisted||o.touched),J=z=>()=>{const Be=Kt(h,e,z,Ve());O(),Be&&!ge(Be)?x(Be.id):Be&&de(Be.id)};return[{header:a?{separator:"Separator",spacer:"Spacer","theme-switcher":"Theme switcher"}[o.type]:o.text||y?.label||"(untitled)"},...a?[]:[{label:"Edit\u2026",icon:"edit",action:()=>x(e)}],{type:"divider"},{label:"Add item below",icon:"plus",action:J("item")},{label:"Add sub-item",icon:"corner-down-right",disabled:a,action:()=>{const z=Mt(h,e,Ve());O(),z&&x(z.id)}},{label:"Add separator below",icon:"minus",action:J("separator")},{label:"Add spacer below",icon:"move",action:J("spacer")},...P?[]:[{label:"Add theme switcher below",icon:"palette",action:J("theme-switcher")}],{type:"divider"},{label:"Move up",icon:"arrow-up",shortcut:"Alt+\u2191",disabled:s<=0,action:()=>Me(e)},{label:"Move down",icon:"arrow-down",shortcut:"Alt+\u2193",disabled:s<0||s>=v.length-1,action:()=>ze(e)},{label:"Indent",icon:"chevron-right",shortcut:"Alt+\u2192",disabled:s<=0||ge(v[s-1]),action:()=>Ie(e)},{label:"Outdent",icon:"chevron-left",shortcut:"Alt+\u2190",disabled:o.parentId==null,action:()=>Ue(e)},{type:"divider"},...a?[]:[{label:o.hidden?"Show":"Hide",icon:o.hidden?"eye":"eye-off",disabled:c,action:()=>ue(e)}],{label:"Duplicate",icon:"copy",disabled:!!y,action:()=>{const z=Ut(h,e,Ve);O(),z&&de(z.id)}},...y&&!y.orphan&&d?[{label:"Back to its default place",icon:"rotate-ccw",action:()=>{Te(o),O()}}]:[],{label:c?"Remove - always in the sidebar":y&&!y.orphan?"Remove - a Tool is hidden, not removed":"Remove\u2026",icon:"trash",danger:!0,disabled:!_e(o),action:()=>Fe(e)}]}const me=E.contextMenu(T,{cursor:"glyph",match:".menu-tree-row",inherit:!1,density:"compact",items:e=>Je(e.dataset.id)});i(()=>me?.destroy?.());const Qe=Tt(T,()=>h,O,{items:".menu-tree-row",handle:".menu-tree-grip"});i(()=>Qe?.destroy());const We=e=>{const o=e.target.closest(".menu-tree-row");if(!o)return;const a=o.dataset.id;if(e.target.closest(".mex-kebab")){at(e.target.closest(".mex-kebab"));return}if(e.target.closest(".me-hidden")){ue(a);return}if(e.target.closest(".me-edit")){x(a);return}e.target.closest("select, .menu-tree-grip, button")||x(a)},Ke=e=>{const o=e.target.closest(".menu-tree-row");if(!o||e.target!==o){o&&e.key==="F10"&&e.shiftKey&&(e.preventDefault(),at(o));return}const a=o.dataset.id;if(e.key==="F10"&&e.shiftKey)e.preventDefault(),at(o);else if(e.key==="Enter")e.preventDefault(),x(a);else if(e.key==="Delete")e.preventDefault(),Fe(a);else if(e.altKey&&e.key==="ArrowUp")e.preventDefault(),Me(a);else if(e.altKey&&e.key==="ArrowDown")e.preventDefault(),ze(a);else if(e.altKey&&e.key==="ArrowRight")e.preventDefault(),Ie(a);else if(e.altKey&&e.key==="ArrowLeft")e.preventDefault(),Ue(a);else if(e.key==="ArrowDown"||e.key==="ArrowUp"){const c=[...T.querySelectorAll(".menu-tree-row")],v=c.indexOf(o)+(e.key==="ArrowDown"?1:-1);c[v]&&(e.preventDefault(),c[v].focus())}},qe=e=>{if(!e.target.matches(".me-spacer-size"))return;const o=ce(e.target.closest(".menu-tree-row")?.dataset.id);o&&(o.size=e.target.value,X())};T.addEventListener("click",We),T.addEventListener("keydown",Ke),T.addEventListener("change",qe),i(()=>{T.removeEventListener("click",We),T.removeEventListener("keydown",Ke),T.removeEventListener("change",qe)});const t=e=>n.find(e),we=t("#me-float-accent"),pe=t("#me-float-accent-hex"),He=()=>{pe.get(0).style.display=we.val()==="__custom__"?"":"none"},Ne=()=>we.val()==="__custom__"?pe.val():we.val(),W=t("#me-highlight-accent"),_=t("#me-highlight-accent-hex"),Oe=()=>{_.get(0).style.display=W.val()==="__custom__"?"":"none"},Ze=()=>W.val()==="__custom__"?_.val():W.val(),Se=()=>{const e=!!(t("#me-entrance-enabled").get(0)||{}).checked;t(".dm-entrance-field").each(function(){this.style.display=e?"":"none"});const o=t("#me-position").val()==="floating";t(".dm-float-field").each(function(){this.style.display=o?"":"none"}),o&&He();const a=t("#me-orientation").val()==="vertical";t(".dm-vertical-field").each(function(){this.style.display=a?"":"none"}),t(".dm-dock-rail-field").each(function(){this.style.display=a&&!o?"":"none"}),t(".dm-dock-bar-field").each(function(){this.style.display=!a&&!o?"":"none"});const c=!!(t("#me-dock-collapsible").get(0)||{}).checked,v=!!(t("#me-dock-autohide").get(0)||{}).checked;t(".dm-dock-collapsed-field").each(function(){this.style.display=c&&!v?"":"none"});const s=!!(t("#me-float-collapsible").get(0)||{}).checked;t(".dm-float-collapsed-field").each(function(){this.style.display=o&&s?"":"none"});const y=!a&&!P;t(".dm-look-field").each(function(){this.style.display=y?"":"none"});const d=y&&t("#me-look-dropdown").val()==="cards";t(".dm-look-cards-field").each(function(){this.style.display=d?"":"none"})},xe=(e,o,a,c)=>{const v=t(e).get(0);v&&(v.innerHTML="",v.add(new Option(c,"")),o.forEach(s=>v.add(new Option(a[s]||s,s))))};xe("#me-highlight-hover",dt,mt,"- theme default -"),xe("#me-highlight-active",ut,pt,"- theme default -");{const e=t("#me-entrance-animation").get(0);e&&(e.innerHTML="",nt.forEach(o=>e.add(new Option(Et[o]||o,o))))}const Re=t("#me-bind-slot"),Ae=t("#me-bind-projects"),g=t("#me-bind-pages");Re.html('<option value="">- not bound -</option>'+(ie||[]).map(e=>`<option value="${A(e.slot)}">${A(e.label||e.slot)} (${A(e.slot)})</option>`).join("")),Ae.html(le.map(e=>`<option value="${A(e.slug)}">${A(e.name||e.slug)}</option>`).join("")),t("#me-bind-page-pick").html('<option value="">- pick a page -</option>'+ve.map(e=>{const o=e.urlPath||e.url||"";return o?`<option value="${A(o)}">${A(e.title||o)} - ${A(o)}</option>`:""}).join(""));const p=()=>zt({slot:Re.val(),projects:Array.from(Ae.get(0).selectedOptions).map(e=>e.value),pagesText:g.val()});function F(){const e=p(),o=t("#me-bind-summary");if(!e){o.html('<span class="text-muted">Not bound - this menu renders only where it is mapped or placed by a shortcode.</span>');return}const a=[];e.projects.length&&a.push(`pages in ${e.projects.map(s=>`<code>${A(s)}</code>`).join(", ")}`),e.pages.length&&a.push(`pages matching ${e.pages.map(s=>`<code>${A(s)}</code>`).join(", ")}`);const c=e.pages.filter(s=>!/^\/[^\s?#*]*(\/\*)?$|^\/\*$/.test(s)),v=c.length?`<div class="text-danger" style="margin-top:.4rem">Not valid page patterns: ${c.map(s=>`<code>${A(s)}</code>`).join(", ")}</div>`:"";o.html(a.length?`Takes over the <code>${A(e.slot)}</code> slot on ${a.join(" and ")}.${v}`:`<span class="text-warning">Slot <code>${A(e.slot)}</code> selected but no project or page chosen - the binding will never match.</span>`)}const{getProjectFromHash:he}=await import("../lib/project-context.js");if(!w())return;const Ee=C?he():null;t("#me-project").html('<option value="">- none -</option>'+le.map(e=>`<option value="${A(e.slug)}">${A(e.name||e.slug)}</option>`).join(""));function fe(e){h=Ce(e.items),t("#me-variant").val(e.variant||""),t("#me-position").val(e.position||"");const o=t("#me-appearOnHover").get(0);o&&(o.checked=e.appearOnHover===!0),t("#me-float-anchor").val(e.float?.anchor||"TL"),t("#me-float-offsetX").val(e.float?.offsetX??""),t("#me-float-offsetY").val(e.float?.offsetY??""),t("#me-float-width").val(e.float?.width??""),t("#me-float-radius").val(e.float?.radius??""),t("#me-float-shadow").val(e.float?.shadow||""),t("#me-float-opacity").val(e.float?.opacity??""),t("#me-float-title").val(e.float?.title||"");for(const[s,y]of[["#me-float-collapsible",e.float?.collapsible],["#me-float-collapsed",e.float?.collapsed],["#me-float-movable",e.float?.movable],["#me-float-resizable",e.float?.resizable]]){const d=t(s).get(0);d&&(d.checked=y===!0)}const a=e.float?.accent||"";we.val(a.startsWith("#")?"__custom__":a),a.startsWith("#")&&pe.val(a),He(),t("#me-orientation").val(e.orientation||""),t("#me-align").val(e.align||""),t("#me-mobile-hamburger").get(0).checked=e.mobile?.hamburgerOnly===!0,t("#me-mobile-brand").get(0).checked=e.mobile?.hideBrand===!0,t("#me-look-bar").val(e.look?.bar||""),t("#me-look-brand").val(e.look?.brand||""),t("#me-look-strip").val(e.look?.strip||""),t("#me-look-dropdown").val(e.look?.dropdown||""),t("#me-look-tiles").val(e.look?.tiles||""),t("#me-look-morph").get(0).checked=e.look?.morph===!0,t("#me-look-shrink").get(0).checked=e.look?.shrink===!0,t("#me-side").val(e.side||"left"),t("#me-dock-width").val(e.dock?.width??"");for(const s of["collapsible","collapsed","autohide","hideOnScroll"]){const y=t(`#me-dock-${s}`).get(0);y&&(y.checked=e.dock?.[s]===!0)}t("#me-class").val(e.class||""),t("#me-highlight-hover").val(e.highlight?.hover||""),t("#me-highlight-active").val(e.highlight?.active||"");const c=e.highlight?.accent||"";W.val(c.startsWith("#")?"__custom__":c),c.startsWith("#")&&_.val(c),Oe();{const s=t("#me-entrance-enabled").get(0);s&&(s.checked=e.entrance?.enabled===!0)}t("#me-entrance-animation").val(nt.includes(e.entrance?.animation)?e.entrance.animation:nt[0]),t("#me-entrance-stagger").val(e.entrance?.stagger??""),t("#me-entrance-duration").val(e.entrance?.duration??""),t("#me-fontFamily").val(e.style?.fontFamily||""),t("#me-fontSize").val(e.style?.fontSize||""),t("#me-fontWeight").val(e.style?.fontWeight||""),t("#me-letterSpacing").val(e.style?.letterSpacing||""),t("#me-iconSize").val(e.style?.iconSize||""),Re.val(e.binding?.slot||"");const v=new Set(e.binding?.projects||[]);Array.from(Ae.get(0).options).forEach(s=>{s.selected=v.has(s.value)}),g.val((e.binding?.pages||[]).join(`
|
|
30
|
+
`)),t("#me-slug").val(e.slug||""),t("#me-name").val(e.name||""),t("#me-description").val(e.description||""),t("#me-project").val(e.meta?.project||Ee||""),Se(),F(),ye()}const $=e=>!!(t(e).get(0)||{}).checked;function ne(){const e={variant:t("#me-variant").val(),position:t("#me-position").val(),appearOnHover:$("#me-appearOnHover"),floatAnchor:t("#me-float-anchor").val(),floatOffsetX:t("#me-float-offsetX").val(),floatOffsetY:t("#me-float-offsetY").val(),floatWidth:t("#me-float-width").val(),floatRadius:t("#me-float-radius").val(),floatShadow:t("#me-float-shadow").val(),floatAccent:Ne(),floatOpacity:t("#me-float-opacity").val(),floatTitle:t("#me-float-title").val(),floatCollapsible:$("#me-float-collapsible"),floatCollapsed:$("#me-float-collapsed"),floatMovable:$("#me-float-movable"),floatResizable:$("#me-float-resizable"),highlightHover:t("#me-highlight-hover").val(),highlightActive:t("#me-highlight-active").val(),highlightAccent:Ze(),class:t("#me-class").val(),entranceEnabled:$("#me-entrance-enabled"),entranceAnimation:t("#me-entrance-animation").val(),entranceStagger:t("#me-entrance-stagger").val(),entranceDuration:t("#me-entrance-duration").val(),orientation:t("#me-orientation").val(),align:t("#me-align").val(),side:t("#me-side").val(),fontFamily:t("#me-fontFamily").val(),fontSize:t("#me-fontSize").val(),fontWeight:t("#me-fontWeight").val(),letterSpacing:t("#me-letterSpacing").val(),iconSize:t("#me-iconSize").val(),dockWidth:t("#me-dock-width").val(),dockCollapsible:$("#me-dock-collapsible"),dockCollapsed:$("#me-dock-collapsed"),dockAutohide:$("#me-dock-autohide"),mobileHamburgerOnly:$("#me-mobile-hamburger"),mobileHideBrand:$("#me-mobile-brand"),lookBar:t("#me-look-bar").val(),lookDropdown:t("#me-look-dropdown").val(),lookBrand:t("#me-look-brand").val(),lookStrip:t("#me-look-strip").val(),lookTiles:t("#me-look-tiles").val(),lookMorph:$("#me-look-morph"),lookShrink:$("#me-look-shrink"),dockHideOnScroll:$("#me-dock-hideOnScroll")},o={slug:t("#me-slug").val(),name:t("#me-name").val(),description:t("#me-description").val(),project:t("#me-project").val()};return qt(e,o,p(),Dt(h),f.meta||{})}const oe=e=>[...Gt(e),...ht(e.class).map(o=>`CSS classes: ${o} (Behaviour tab).`)];let lt=!1,et=null;function X(){if(!U)return;const e=ne();et=e,j.value=Ye(e)!==U,je.value=j.peek()||lt?oe(e).join(" "):""}const Ge=e=>{e.target.closest("#me-items-list")||(e.target.matches("#me-position, #me-orientation, #me-look-dropdown, #me-float-collapsible, #me-dock-collapsible, #me-dock-autohide, #me-entrance-enabled")&&Se(),e.target.matches("#me-float-accent")&&He(),e.target.matches("#me-highlight-accent")&&Oe(),e.target.closest("#me-binding")&&F(),X())};l.addEventListener("input",Ge),l.addEventListener("change",Ge),i(()=>{l.removeEventListener("input",Ge),l.removeEventListener("change",Ge)}),t("#me-bind-page-add").on("click",()=>{const e=t("#me-bind-page-pick").val();if(!e)return;const o=g.val().split(`
|
|
31
|
+
`).map(a=>a.trim()).filter(Boolean);o.includes(e)||o.push(e),g.val(o.join(`
|
|
32
|
+
`)),F(),X()}),fe(f),U=Ye(ne()),X();const st=()=>{try{S.remove?S.remove(V):S.set(V,null)}catch{}};async function tt(){if(re.peek())return;const e=ne(),o=oe(e);if(o.length){lt=!0,X(),E.toast(o.join(" "),{type:"warning",duration:7e3});return}re.value=!0;let a=null;try{if(C)await D.menus.create(e);else if(e.slug!==f.slug){await D.menus.create(e);const c=await D.menuLocations.get();let v=!1;for(const[s,y]of Object.entries(c))y===f.slug&&(c[s]=e.slug,v=!0);v&&await D.menuLocations.save(c),await D.menus.remove(f.slug)}else{const c=await kt(e);if(!c)return;a=c.revision}if(U=Ye(e),j.value=!1,st(),Ft("#/menus"),E.toast("Saved.",{type:"success"}),C||e.slug!==f.slug)location.hash="#/menus/edit/"+encodeURIComponent(e.slug);else{f={...f,...e,revision:a};for(const c of h)c.tool&&c.touched&&(c.tool={...c.tool,persisted:!0});ye(),U=Ye(ne()),X(),yt(n.get(0),e.name||e.slug)}if(P)try{document.dispatchEvent(new CustomEvent("dm:sidebar-rebuild"))}catch{}}catch(c){E.toast(`Save failed: ${c.message||c}`,{type:"error"})}finally{re.value=!1}}async function kt(e){try{return await D.menus.update(f.slug,{...e,revision:f.revision})}catch(a){if(a.status!==409)throw a}if(await E.confirm(`The menu "${De(f.name||f.slug)}" was changed somewhere else after you opened it - in another tab, by someone else, or on the server. Saving now would undo those changes.<br><br><strong>OK</strong> saves yours over them. <strong>Cancel</strong> loads the latest version, and offers your changes back as a draft.`))return D.menus.update(f.slug,e);try{S.set(V,{menu:e,base:U,at:new Date().toISOString()})}catch{}return j.value=!1,location.reload(),null}async function wt(){!j.peek()||!await E.confirm("Throw away the changes you have not saved?")||(fe(f),X())}E.tabs(t("#me-tabs").get(0));const St=M.applyBindings({dirty:j,saving:re,problems:je,itemCount:Le},l,{methods:{save:()=>tt(),discard:()=>wt(),addItem:()=>B("item"),addSeparator:()=>B("separator"),addSpacer:()=>B("spacer"),addSwitcher:()=>B("theme-switcher")}});i(()=>St.dispose()),i(Pt(l,{position:"bottom"})),P&&t("#me-add-switcher").hide(),yt(n.get(0),C?"New menu":f.name||f.slug),ot(n,{id:"menu-editor-back",icon:"arrow-left",title:"All menus",onClick:()=>{location.hash="#/menus"}}),ot(n,{id:"menu-editor-locations",icon:"map-pin",title:"Menu locations",onClick:()=>{location.hash="#/menu-locations"}}),ot(n,{id:"menu-editor-save",icon:"save",title:"Save (Ctrl+S)",onClick:()=>tt()});const it=e=>{if(!(!(e.ctrlKey||e.metaKey)||e.key!=="s")){if(e.preventDefault(),e.stopImmediatePropagation(),document.querySelector(".me-item-form")?.offsetParent){E.toast("Apply the item first (Ctrl+Enter), then Save.",{type:"info"});return}tt()}};document.addEventListener("keydown",it,!0),i(()=>document.removeEventListener("keydown",it,!0));const rt=()=>{if(!(!j.peek()||!et))try{S.set(V,{menu:et,base:U,at:new Date().toISOString()})}catch{}},ct=e=>{j.peek()&&(rt(),e.preventDefault(),e.returnValue="")};window.addEventListener("beforeunload",ct),i(()=>window.removeEventListener("beforeunload",ct)),q={path:L,dirty:()=>j.peek(),name:()=>t("#me-name").val()||f.name||f.slug||"new menu",dispose(){r=!0,rt(),u.splice(0).reverse().forEach(e=>{try{e()}catch{}})}},I.scan(l);let ae=null;try{ae=S.get(V)}catch{}if(ae?.menu){st();const e=ae.base&&ae.base!==U,o=ae.at?` (${Xt(ae.at)})`:"",a=C?"the new menu you started":`the menu "${De(f.name||f.slug)}"`,c=e?`You left unsaved changes to ${a}${o}, but the saved menu has changed since. Restore your version anyway?`:`You left unsaved changes to ${a}${o}. Restore them?`;await E.confirm(c)&&w()&&(fe({...ae.menu,meta:{...f.meta||{},...ae.menu.meta||{}}}),X())}}};
|
package/admin/js/views/search.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import{apiRequest as
|
|
1
|
+
import{apiRequest as A}from"../api.js";import{helpPopovers as ge}from"../lib/help-popover.js";import{openMenuAt as oe,sidebarCount as ye}from"../lib/tool-kit.js";import{POSITION_LABEL as xe,clampNotes as Se,draftQuestion as we,formFromSettings as D,keptDraft as ke,searchDraftKey as Me,liveChips as Ee,maxRadius as Te,payloadFromForm as l,previewState as ne,radiusForSize as Le,resultRow as Re,snapshot as d}from"../lib/search-settings-arrange.js";import{fmtDateTime as Pe}from"../lib/dates.js";import{SHAPES as ze,whileLoading as qe}from"../lib/loading.js";const Oe="#/search",Ae='<span class="ssx-chip" data-bind-class="cls">{{text}}</span>',De=`
|
|
2
2
|
<div class="ssx-row" data-url="{{url}}" tabindex="0">
|
|
3
3
|
<span class="ssx-rank">{{rank}}</span>
|
|
4
4
|
<span class="ssx-main"><strong>{{title}}</strong><small>{{meta}}</small><span class="ssx-snippet">{{snippet}}</span></span>
|
|
5
5
|
<span class="ssx-chip">{{score}}</span>
|
|
6
6
|
<button type="button" class="ssx-kebab" aria-label="Actions for {{title}}" title="Actions">\u22EE</button>
|
|
7
|
-
</div>`;
|
|
7
|
+
</div>`;export async function mountSearchSettings(H,{onDirty:ae=()=>{},onReady:re=()=>{}}={}){const K=[],r=e=>(K.push(e),e);let B=!1;const a=H.get(0).querySelector(".ssx")||H.get(0),T=()=>!B&&a.isConnected;let L="";try{L=S.get("auth_user")?.id||""}catch{}const c=Me(L),b=a.querySelector(".ssx-loading"),Q=a.querySelector(".ssx-body");let $,C=null;try{[$,C]=await qe(b,Promise.all([A("/search/settings"),A("/search/_count").catch(()=>null)]),ze.rows)}catch(e){if(!a.isConnected)return;b&&(b.hidden=!0);const t=a.querySelector(".ssx-failed");t&&(t.textContent=`Could not load the search settings: ${e.message||e}. Reload the page to try again.`,t.hidden=!1);return}if(!a.isConnected)return;b&&(b.hidden=!0),Q&&(Q.hidden=!1);const R=M.observable($),V=D($),s={};for(const[e,t]of Object.entries(V))s[e]=M.observable(t);const h=()=>Object.fromEntries(Object.keys(s).map(e=>[e,s[e].peek()])),p=()=>Object.fromEntries(Object.keys(s).map(e=>[e,s[e].value])),g=M.observable(d(V)),f=M.computed(()=>d(p())!==g.value);r(M.effect(()=>ae(f.value)));const P=M.observable(!1);let z=g.peek();const J=M.observable(C?{searchable:C.count}:null),Y=M.observable([]);r(M.effect(()=>{Y.value=Ee(R.value,J.value).map(e=>({text:e.text,cls:e.tone?`is-${e.tone}`:""}))}));let m=Number(s.iconSize.peek())||52;r(M.effect(()=>{const e=Number(s.iconSize.value)||52;if(e===m)return;const t=Le(s.iconRadius.peek(),m,e);m=e,String(t)!==String(s.iconRadius.peek())&&(s.iconRadius.value=String(t))}));const ie=M.computed(()=>Te(s.iconSize.value)),G=a.querySelector("#ssx-radius");r(M.effect(()=>{G&&(G.max=String(ie.value))}));const y=M.observable(""),x=M.observable([]),q=M.observable(""),O=M.observable(""),ce=M.computed(()=>ne(y.value,s.minQueryLength.value).note);let N=0,_=null;async function le(){const e=ne(y.peek(),s.minQueryLength.peek()),t=++N;if(!e.run){x.value=[],O.value="",q.value="";return}const n=l(h()).maxResults;try{const o=await A(`/search/preview?q=${encodeURIComponent(y.peek().trim())}&max=${n}`);if(t!==N||!T())return;x.value=(o.results||[]).map(Re),q.value="";const i=x.peek().length;O.value=i?`${i} result${i===1?"":"s"} (showing up to ${o.maxResults}) from ${o.coverage?.searchable??"?"} searchable pages.`:`Nothing found in ${o.coverage?.searchable??"?"} searchable pages.`,o.coverage&&(J.value=o.coverage)}catch(o){if(t!==N)return;x.value=[],O.value="",q.value=`Search failed: ${o.message||o}`}}r(M.effect(()=>{y.value,s.maxResults.value,clearTimeout(_),_=setTimeout(le,l(h()).debounceMs)})),r(()=>clearTimeout(_));async function W(){if(P.peek())return;P.value=!0;const e=h(),t=d(e);try{const o=(await A("/search/settings",{method:"PUT",body:JSON.stringify(l(e))}))?.settings||l(e),i=D(o);if(!T()){ue(d(i),t);return}if(R.value=o,d(h())===t){m=Number(i.iconSize)||52;for(const[be,he]of Object.entries(i))s[be].value=he}return g.value=d(i),z=g.peek(),f.peek()||w(),ye(Oe),E.toast(o.enabled?"Search settings saved.":"Saved - search is off on the public site.",{type:"success"}),!0}catch(n){return E.toast(`Search not saved: ${n.message||n}`,{type:"error"}),!1}finally{P.value=!1}}function w(){if(c)try{S.remove?S.remove(c):S.set(c,null)}catch{}}function ue(...e){if(!c)return;let t=null;try{t=S.get(c)}catch{return}if(t?.form)try{e.includes(d(t.form))&&w()}catch{}}function X(){const e=D(R.peek());m=Number(e.iconSize)||52;for(const[t,n]of Object.entries(e))s[t].value=n}a.querySelector(".ssx-chips").innerHTML=Ae;const v=a.querySelector(".ssx-list");v.innerHTML=De;const k=e=>`${e}px`,pe={...s,dirty:f,saving:P,chips:Y,query:y,results:x,previewNote:ce,previewError:q,previewMeta:O,clampText:M.computed(()=>Se(p()).join(" ")),sizeText:M.computed(()=>k(l(p()).iconSize)),radiusText:M.computed(()=>k(l(p()).iconRadius)),sizePx:M.computed(()=>k(l(p()).iconSize)),radiusPx:M.computed(()=>k(l(p()).iconRadius)),glyphPx:M.computed(()=>k(Math.round(l(p()).iconSize*20/52))),positionText:M.computed(()=>xe[s.position.value]||s.position.value)},de=E.tabs(a.querySelector(".ssx-tabs"),{tabSelector:".tab-item",panelSelector:".tab-panel"});r(()=>de.destroy?.());const fe=M.applyBindings(pe,a,{methods:{save:()=>W(),discard:()=>X(),useNavbar:()=>{s.displayMode.value="navbar"},useFloating:()=>{s.displayMode.value="floating"},setPos:e=>{s.position.value=e}}});r(()=>fe.dispose()),r(ge(a,{position:"bottom"}));const me=e=>[{header:e},{label:"Open on the site",icon:"external-link",action:()=>window.open(e,"_blank","noopener")},{label:"Edit the page",icon:"edit",action:()=>{location.hash=`#/pages/edit${e}`}},{label:"Copy address",icon:"copy",action:()=>{const t=new URL(e,location.origin).href;navigator.clipboard?.writeText(t).then(()=>E.toast("Address copied.",{type:"success"}),()=>E.toast(t,{type:"info",duration:8e3}))}}],ve=E.contextMenu(v,{cursor:"glyph",match:".ssx-row",inherit:!1,density:"compact",items:e=>me(e.dataset.url)});r(()=>ve?.destroy?.());const Z=e=>{const t=e.target.closest(".ssx-row");if(!t)return;const n=e.target.closest(".ssx-kebab");if(n){oe(n);return}window.open(t.dataset.url,"_blank","noopener")},ee=e=>{const t=e.target.closest(".ssx-row");t&&(e.key==="F10"&&e.shiftKey?(e.preventDefault(),oe(t)):e.key==="Enter"&&e.target===t&&(e.preventDefault(),e.stopPropagation(),window.open(t.dataset.url,"_blank","noopener")))};v.addEventListener("click",Z),v.addEventListener("keydown",ee),r(()=>{v.removeEventListener("click",Z),v.removeEventListener("keydown",ee)});let j=null;try{j=c?S.get(c):null}catch{}const u=ke(j,g.peek(),L);let F=!u;const te=()=>{if(c)if(f.peek())try{S.set(c,{owner:L,form:h(),was:z,at:new Date().toISOString()})}catch{}else F&&w()},se=e=>{f.peek()&&(te(),e.preventDefault(),e.returnValue="")};window.addEventListener("beforeunload",se),r(()=>window.removeEventListener("beforeunload",se));const U={save:W,discard:X,dirty:()=>f.peek(),dispose(){B=!0,te(),K.splice(0).reverse().forEach(e=>{try{e()}catch{}})}};if(re(U),!T())return U;if(I.scan(a),!u&&j&&w(),u){const e=we({when:u.at?Pe(u.at):"",stale:u.stale});if(await new Promise(n=>setTimeout(n,0)),await E.confirm(e.text,{title:e.title,confirmText:e.confirmText,cancelText:e.cancelText}))w(),F=!0;else if(T()){F=!0;const n={...D(R.peek()),...u.form};m=Number(n.iconSize)||52;for(const[o,i]of Object.entries(n))s[o]&&(s[o].value=i);z=u.was||z}}return U}export const searchView={template:"",onMount(){location.replace("#/settings?tab=search")}};
|