domma-cms 1.15.0 → 1.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +8 -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/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/search.js +2 -2
- package/admin/js/views/seo.js +50 -42
- package/admin/js/views/settings.js +3 -3
- package/docs/DommaFeatures.md +1 -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/seo.js +14 -3
- package/server/services/adminBrand.js +1 -0
- package/server/services/googleBusiness.js +225 -0
- package/server/services/renderer.js +2 -0
- package/server/templates/page.html +1 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import{api as g,getUser as w,isAuthenticated as
|
|
2
|
-
<summary${v}>${
|
|
1
|
+
import{api as g,getUser as w,isAuthenticated as ke}from"../api.js";import{createRequestQueue as qe}from"./background-requests.js";import{requestSidebarCounts as Ce}from"./sidebar-counts.js";import{beginSidebarCounts as Ae,publishSidebarCount as Fe}from"./sidebar-count-cache.js";import{isPathAllowed as Le}from"./admin-scope.js";import{cleanNotify as Ee,noticeFromCount as je}from"./sidebar-notify.js";import{colourToCss as te,applyFloat as Be,applyHighlight as Oe,itemHighlightAttrs as Me}from"/public/js/menu-decor.mjs?v=20261014-menu-entrance";import{placeTools as Te,ensureLockedTools as Ne,managePluginsItem as He,stripItemByUrl as Re,insertFoldersBeforeSystem as Ie,pruneEmptyToolsFolder as Pe,sidebarStateKey as ne,canAny as W,filterByPermission as _e,MANAGE_PLUGINS_URL as se}from"./sidebar-grouping.js";import{arrangeButtonMarkup as Ue,canArrange as ze,startArrange as We}from"./sidebar-arrange.js";import{collapseButtonMarkup as De,wireSidebarSize as Ge,COLLAPSED_WIDTH as Ke}from"./sidebar-size.js";import{FREQUENT_MAX as oe,addFrequent as Ve,frequentFolder as Xe,moveFrequent as ie,readFrequent as K,readFrequentHidden as Ze,removeFrequent as Je,saveFrequent as Ye,syncFrequent as Qe,writeFrequent as et,writeFrequentHidden as ae}from"./sidebar-frequent.js";import{hotkeyFor as re,hotkeyTitle as tt,setHotkeyTree as nt}from"./hotkeys-ui.js";import{hotkeyLabel as st}from"./hotkeys.js";function m(e){return String(e??"").replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""")}const ot=[{text:"Dashboard",url:"#/",icon:"home"},{text:"Site menus",url:"#/menus",icon:"menu",permission:"menus"}];async function it(e){if(!W(e,"projects"))return[];let n=[];try{n=await g.projects.listWithCounts()}catch{return[]}if(!Array.isArray(n))return[];const t=[{key:"pages",text:"Pages",icon:"file-text",path:"/pages"},{key:"collections",text:"Collections",icon:"database",path:"/collections"},{key:"forms",text:"Forms",icon:"layout",path:"/forms"},{key:"actions",text:"Actions",icon:"zap",path:"/actions"},{key:"menus",text:"Site menus",icon:"menu",path:"/menus"},{key:"blocks",text:"Blocks",icon:"box",path:"/blocks"},{key:"components",text:"Components",icon:"component",path:"/components"},{key:"views",text:"Views",icon:"eye",path:"/views"},{key:"roles",text:"Roles",icon:"shield",path:"/roles"},{key:"users",text:"Users",icon:"users",path:"/users"},{key:"apis",text:"APIs",icon:"code",path:"/apis"}];return n.map(o=>{const r="#/projects/"+encodeURIComponent(o.slug),u=o.counts||{},i=[{text:"Overview",url:r,icon:o.icon||"folder"}];for(const d of t){const l=Number(u[d.key])||0;l<=0||i.push({text:`${d.text} (${l})`,url:r+d.path,icon:d.icon})}i.push({text:"Settings",url:r+"/settings",icon:"settings"});const a=o.enabled===!1;return{text:o.name||o.slug,key:`project.${o.slug}`,icon:o.icon||"folder",items:i,...a&&{colour:"neutral",badge:{text:"off"}}}})}function at(e,n){for(const t of e)if(!t.hidden&&(t.key==="projects"||!t.key&&(t.text||"").toLowerCase()==="projects")){t.items=Array.isArray(t.items)?t.items.slice():[],t.items.push(...n);return}}function le(e,n){const t=de.includes(n.tone)?n.tone:"info",o=n.label||String(n.count);n.details?R.set(e,n.details):R.delete(e);const s=!n.inventory&&j.some(r=>r.url===e&&!r.inventory)?" data-live":"";return`<span class="sidebar-badge sidebar-badge--${t}" data-count-for="${m(e)}"${s} data-badge-label="${m(o)}" aria-label="${m(o)}">${m(String(n.count))}</span>`}function ce(e){for(const n of e.querySelectorAll("details")){const t=n.querySelector(":scope > summary");if(!t)continue;let o=0,s=!1;const r=[];for(const l of n.querySelectorAll(":scope > .sidebar-children .sidebar-badge[data-live]")){const f=Number(l.textContent)||0;f&&(o+=f,s||(s=l.classList.contains("sidebar-badge--danger")),r.push({text:l.closest("a")?.querySelector(".sidebar-text")?.textContent.trim()??"",meta:l.dataset.badgeLabel,tone:l.classList.contains("sidebar-badge--danger")?"danger":void 0}))}const u=`folder:${n.dataset.stateKey||t.textContent.trim()}`;let i=t.querySelector(":scope > .sidebar-badge--sum");if(!o){i?.remove(),R.delete(u);continue}i||(i=document.createElement("span"),t.querySelector(".sidebar-text")?.after(i));const a=t.querySelector(".sidebar-text")?.textContent.trim()||"This folder",d=r.map(l=>`${l.text}: ${l.meta}`).join(" \xB7 ");i.className=`sidebar-badge sidebar-badge--sum sidebar-badge--${s?"danger":"info"}`,i.dataset.countFor=u,i.dataset.badgeLabel=d,i.setAttribute("aria-label",d),i.textContent=String(o),R.set(u,{title:a,items:r})}}const R=new Map;function rt(e,n){if(!e.url||!n)return"";const t=n[e.url];return!t||!(t.count>0)&&!t.showZero?"":le(e.url,t)}const de=["info","attention","danger","live"],lt={"#/pages":["page","pages"],"#/media":["file in the media library","files in the media library"],"#/menus":["menu","menus"],"#/context-menus":["context menu","context menus"],"#/theme":["element override in the theme","element overrides in the theme"],"#/collections":["collection","collections"],"#/forms":["form","forms"],"#/views":["view","views"],"#/actions":["action","actions"],"#/blocks":["block","blocks"],"#/components":["component","components"],"#/users":["user","users"],"#/plugins":["plugin installed","plugins installed"],"#/system/notifications":["unread notification","unread notifications"]};function D(e,{fallbackLabel:n="",tone:t="info"}={}){const o=Number(typeof e=="object"&&e!==null?e.count:e);if(!Number.isFinite(o)||o<0)return null;const s=typeof e?.label=="string"&&e.label||(n?`${o} - ${n}`:String(o)),r={count:Math.round(o),label:s,tone:de.includes(e?.tone)?e.tone:t};e?.details&&typeof e.details=="object"&&(r.details=e.details),e?.inventory===!0&&(r.inventory=!0),e?.showZero===!0&&(r.showZero=!0);const u=Ee(e?.notify);return u&&(r.notify=u),r}function ue(e,n,t=null,{inFrequent:o=!1}={}){if(e&&e.type==="separator")return'<hr class="sidebar-divider">';if(e&&e.type==="theme-switcher")return"";if(e&&e.type==="spacer"){const b=["xs","sm","md","lg","xl","flex"].includes(e.size)?e.size:"md";return`<div class="sidebar-spacer sidebar-spacer--${m(b)}" aria-hidden="true"></div>`}const s=String(e.text||"?").trim().charAt(0).toUpperCase()||"?",r=e.icon?`<span data-icon="${m(e.icon)}"></span>`:`<span class="sidebar-initial" aria-hidden="true">${m(s)}</span>`,u=m(e.text||""),i=e.url?m(e.url):"",a=Array.isArray(e.items)&&e.items.length>0,d=e.colour?te(e.colour):"",l=Me(e),f=[l.hover&&` data-menu-item-hover="${m(l.hover)}"`,l.active&&` data-menu-item-active="${m(l.active)}"`].filter(Boolean).join(""),h=[d&&`color:${m(d)}`,l.accent&&`--dm-menu-highlight:${m(l.accent)}`].filter(Boolean).join(";"),v=h?` style="${h}"`:"",k=e.url?tt(e.url,e.text||"",{inFrequent:o}):"",q=k?` title="${m(k)}"`:"";let F="";if(e.badge&&e.badge.text!=null&&e.badge.text!==""){const b=e.badge.variant?te(e.badge.variant):"";F=`<span class="dm-menu-badge"${b?` style="background:${m(b)};color:var(--dm-text-inverse,#fff)"`:""}>${m(String(e.badge.text))}</span>`}if(e.frequent&&e.empty){const b=ne(e,t),A=S.get(b)!==!1;return`<details data-state-key="${m(b)}"${A?" open":""} data-frequent data-frequent-empty>
|
|
2
|
+
<summary${v}>${r} <span class="sidebar-text">${u}</span></summary>
|
|
3
3
|
<div class="sidebar-children"><p class="sidebar-frequent-empty">Right-click any Tool and choose <strong>Add to Frequent</strong> to keep it here.</p></div>
|
|
4
|
-
</details>`}if(
|
|
5
|
-
<summary${v}${q}>${
|
|
4
|
+
</details>`}if(a){const b=ne(e,t),A=S.get(b)!==!1,B=e.items.map(I=>ue(I,n,b,{inFrequent:!!e.frequent})).join(""),L=e.frequent?' data-frequent title="Your Frequent Tools - right-click any Tool to add or remove it"':"";return`<details data-state-key="${m(b)}"${A?" open":""}${L}>
|
|
5
|
+
<summary${v}${q}>${r} <span class="sidebar-text">${u}</span>${F}</summary>
|
|
6
6
|
<div class="sidebar-children">${B}</div>
|
|
7
|
-
</details>`}return`<a href="${i}" class="sidebar-link" data-url="${i}"${v}${f}${q}>${a} <span class="sidebar-text">${u}</span>${rt(e,n)}${F}</a>`}async function lt(e=[],n=[],t=null){const o=y=>y.then(C=>Array.isArray(C)?C.length:Array.isArray(C?.entries)?C.entries.length:0).catch(()=>0),s=fe(e,t),a=new Set(s.map(y=>y.url)),u={pages:"#/pages",media:"#/media",menus:"#/menus","context-menus":"#/context-menus",theme:"#/theme",collections:"#/collections",forms:"#/forms",views:"#/views",actions:"#/actions",blocks:"#/blocks",components:"#/components",users:"#/users",plugins:"#/plugins",notifications:"#/system/notifications"},i=(y,C)=>W(n,y)&&!a.has(u[y])?C():Promise.resolve(0),[r,d,l,f,h,v,k,q,F,b,A,B,j,I]=await Promise.all([i("pages",()=>o(g.pages.list())),i("media",()=>o(g.media.list())),i("menus",()=>o(g.menus.list())),i("context-menus",()=>o(g.contextMenus.list())),i("theme",()=>g.theme.get().then(y=>(y?.overrides||[]).length).catch(()=>0)),i("collections",()=>o(g.collections.list())),i("forms",()=>o(g.forms.list())),i("views",()=>o(g.views.list())),i("actions",()=>o(g.actions.list())),i("blocks",()=>o(g.blocks.list())),i("components",()=>o(g.components.list())),i("users",()=>o(g.users.list())),i("plugins",()=>o(g.plugins.list())),i("notifications",()=>g.system?.notifications?.unreadCount?.().then(y=>y?.count??0).catch(()=>0)??Promise.resolve(0))]),P=await pe(s),U={"#/pages":r,"#/media":d,"#/menus":l,"#/context-menus":f,"#/theme":h,"#/collections":v,"#/forms":k,"#/views":q,"#/actions":F,"#/blocks":b,"#/components":A,"#/users":B,"#/plugins":j,"#/system/notifications":I},z={};for(const[y,C]of Object.entries(U)){if(a.has(y))continue;const[Y,G]=at[y]??["item","items"],O=D(C);O&&(z[y]={...O,label:`${O.count} ${O.count===1?Y:G}`})}return{...z,...P}}let L=[];const ct="dm:sidebar-count",dt=6e4,ut=ke(4);function fe(e=[],n=null){const t=[],o=s=>{if(s&&!(n&&typeof s.url=="string"&&s.url.startsWith("#/")&&!Fe(n,s.url.slice(1)))){s.url&&typeof s.countUrl=="string"&&s.countUrl&&t.push({url:s.url,countUrl:s.countUrl,text:s.text||"",countLabel:s.countLabel||"",inventory:s.inventory===!0});for(const a of[...s.children||[],...s.items||[]])o(a)}};for(const s of e??[])o(s?.item??s);return t}async function me(e,{priority:n=!1}={}){try{const t=new Date,o=`${t.getFullYear()}-${String(t.getMonth()+1).padStart(2,"0")}-${String(t.getDate()).padStart(2,"0")}`,s=e.countUrl+(e.countUrl.includes("?")?"&":"?")+`today=${o}`,a=w()?.id,u=await ut(()=>!Se()||w()?.id!==a?null:H.get(s,{priority:"low"}),{key:JSON.stringify([a,s]),priority:n});return D(u,{fallbackLabel:e.countLabel||e.text})}catch{return null}}function V(e,n,t){Ae(n,t),t?.notify&&Ee(w()?.id??null,n,t.notify);const o=[...e.querySelectorAll("a.sidebar-link")].filter(s=>s.dataset.url===n);if(o.length){for(const s of o){const a=s.querySelector(".sidebar-badge[data-count-for]");if(!t||!(t.count>0)&&!t.showZero)a?.remove();else{const u=document.createRange();u.selectNodeContents(s);const i=u.createContextualFragment(le(n,t)).firstElementChild;a?a.replaceWith(i):s.querySelector(".sidebar-text")?.after(i)}}X.set(n,t),ce(e)}}const X=new Map;async function pe(e){const n=await qe(e,{legacy:async t=>{const o=await Promise.all(t.map(async s=>[s.url,await me(s)]));return Object.fromEntries(o)}});return Object.fromEntries(e.map(t=>[t.url,n[t.url]==null?null:D(n[t.url],{fallbackLabel:t.countLabel||t.text})]))}async function Z(e,n=null){const t=w()?.id,o=L;try{const s=n?{[n]:await me(L.find(a=>a.url===n),{priority:!0})}:await pe(L);if(w()?.id!==t||L!==o)return;for(const[a,u]of Object.entries(s)){const i=X.get(a);JSON.stringify(i??null)!==JSON.stringify(u)&&V(e,a,u)}}catch{}}function ft(e){e.dataset.liveCounts||(e.dataset.liveCounts="1",window.addEventListener(ct,n=>{const t=n.detail||{};if(t.url)if(t.count!==void 0){const o=L.find(s=>s.url===t.url);V(e,t.url,D(t,{fallbackLabel:o?.countLabel||o?.text||""}))}else Z(e,t.url)}),setInterval(()=>{!document.hidden&&document.body.contains(e)&&Z(e)},dt),document.addEventListener("visibilitychange",()=>{document.hidden||Z(e)}),mt(e))}function he(e,n,t){const o=(u,i,r)=>{const d=document.createElement(u);return i&&(d.className=i),r!=null&&(d.textContent=String(r)),d};if(!t){e.textContent=n;return}e.appendChild(o("strong","dm-badge-pop-title",t.title||n)),(t.rows?.length||!t.items?.length)&&e.appendChild(o("p","dm-badge-pop-sub",n));const s=Array.isArray(t.rows)?t.rows.slice(0,6):[];if(s.length){const u=o("dl","dm-badge-pop-rows");for(const i of s)u.appendChild(o("dt",null,i.label)),u.appendChild(o("dd",i.tone==="danger"?"is-danger":null,i.value));e.appendChild(u)}const a=Array.isArray(t.items)?t.items.slice(0,6):[];if(a.length){const u=o("ul","dm-badge-pop-items");for(const i of a){const r=o("li",i.tone==="danger"?"is-danger":null);r.appendChild(o("span","dm-badge-pop-text",i.text)),i.meta&&r.appendChild(o("span","dm-badge-pop-meta",i.meta)),u.appendChild(r)}e.appendChild(u)}else t.empty&&e.appendChild(o("p","dm-badge-pop-empty",t.empty))}function mt(e){let n=null,t=null;const o=()=>e.classList.contains("is-collapsed"),s=()=>{clearTimeout(t),n?.remove(),n=null},a=(d,l)=>{s(),t=setTimeout(()=>{n=document.createElement("div"),n.className="dm-help-pop dm-badge-pop",n.setAttribute("role","tooltip"),l(n),document.body.appendChild(n);const f=d.getBoundingClientRect(),h=n.getBoundingClientRect(),v=Math.min(f.right+10,innerWidth-h.width-8);n.style.left=`${Math.max(8,v)}px`,n.style.top=`${Math.max(8,Math.min(f.top+f.height/2-h.height/2,innerHeight-h.height-8))}px`},150)},u=d=>{const l=d?.dataset.badgeLabel;if(!l)return s();a(d,f=>he(f,l,R.get(d.dataset.countFor)))},i=d=>{const l=d.querySelector(":scope > .sidebar-text")?.textContent.trim()||"",f=d.querySelector(":scope > .sidebar-badge[data-badge-label]:not([hidden])");a(d,h=>{const v=f&&R.get(f.dataset.countFor);if(v)return he(h,f.dataset.badgeLabel,{...v,title:v.title||l});if(!f){h.textContent=l;return}const k=document.createElement("strong");k.className="dm-badge-pop-title",k.textContent=l;const q=document.createElement("p");q.className="dm-badge-pop-sub",q.textContent=f.dataset.badgeLabel,h.append(k,q)})},r=d=>{const l=d.closest?.("a.sidebar-link, summary");return l&&e.contains(l)?l:null};e.addEventListener("mouseover",d=>{if(o()){const f=r(d.target);f&&!f.contains(d.relatedTarget)&&i(f);return}const l=d.target.closest?.(".sidebar-badge[data-badge-label]");l&&u(l)}),e.addEventListener("mouseout",d=>{if(o()){const l=r(d.target);l&&!l.contains(d.relatedTarget)&&s();return}d.target.closest?.(".sidebar-badge[data-badge-label]")&&s()}),e.addEventListener("focusin",d=>{const l=d.target.closest?.("a, summary");if(o()&&l&&e.contains(l)&&l.querySelector(":scope > .sidebar-text"))return i(l);const f=l?.querySelector(":scope > .sidebar-badge[data-badge-label]:not([hidden])");f?u(f):s()}),e.addEventListener("focusout",s),e.addEventListener("click",s),window.addEventListener("scroll",s,!0),window.addEventListener("hashchange",s)}async function pt(e=[],n=null){if(n?.title)return n;if(!W(e,"settings"))return{title:"Domma CMS"};try{const t=await g.settings.get();return{...t?.adminBrand,title:t?.adminBrand?.title||t?.title||"Domma CMS"}}catch{return{title:"Domma CMS"}}}export async function renderAdminSidebar({mount:e,permissions:n,adminScope:t=null,brand:o=null}){const s=$(e).get(0);if(!s)return;const a=Ye(g,w()).catch(()=>{}),u=Promise.resolve().then(()=>H.get("/api/sidebar/registered-items")).catch(()=>[]),i=ot(n);let r,d=null,l=null,f=null,h=null,v=null;try{const c=await g.get("/sidebar/menu");r=Array.isArray(c?.items)?c.items:null,d=c?.variant||null,l=c?.position||null,f=c?.style||null,h=c?.float||null,v=c?.highlight||null}catch{r=null}(!r||!r.some(c=>c&&(!c.hidden||c.restricted)))&&(console.warn("[admin-sidebar] No admin-sidebar menu found; using fallback tree"),r=st.slice());let k=[];try{k=await u||[]}catch{}r=He(r,se),r=Me(r,k.filter(c=>(c?.item??c)?.url!==se)),r=Te(r),r=Re(r,[Ne()]);const q=await i;q.length&&it(r,q),r=Pe(r,n,t),r=Ie(r),await a;const F=Ve(r,K(w()),{hidden:Xe(w())});F&&(r=[F,...r]),tt(r);const b=k.filter(c=>W(n,c?.item?.permission));L=fe(b,t);const A=Ce({user:w(),permissions:n,adminScope:t,registered:b,tree:r,actions:{linkItems:ye,folderItems:ve},fetcher:()=>lt(b,n,t)}),B=A.counts,j=await pt(n,o),I=d?` dm-admin-sidebar--${m(d)}`:"";function P(c,x="px"){if(c==null)return c;const p=String(c).trim();return/^\d+(\.\d+)?$/.test(p)?`${p}${x}`:p}let U="";if(f){const c=[];f.fontFamily&&c.push(`font-family: ${f.fontFamily}, sans-serif`),f.fontSize&&c.push(`font-size: ${P(f.fontSize)}`),f.fontWeight&&c.push(`font-weight: ${f.fontWeight}`),f.letterSpacing&&c.push(`letter-spacing: ${P(f.letterSpacing,"em")}`);const x=[];if(c.length&&x.push(`#admin-sidebar .dm-admin-sidebar, #admin-sidebar .dm-admin-sidebar .sidebar-link, #admin-sidebar .dm-admin-sidebar summary, #admin-sidebar .dm-admin-sidebar .sidebar-text { ${c.join("; ")} }`),f.iconSize){const p=P(f.iconSize);x.push(`#admin-sidebar .dm-admin-sidebar [data-icon], #admin-sidebar .dm-admin-sidebar [data-icon] svg { width: ${p} !important; height: ${p} !important; }`)}x.length&&(U=`<style data-admin-sidebar-style>${x.join(" ")}</style>`)}const z=Ue(n)?_e():"",y=String(j.logo||"").trim(),C=/^(?:\/(?!\/)|https?:\/\/)/i.test(y)?y:"",G=`<div class="dm-admin-sidebar-header">${C?`<img src="${m(C)}" alt="" style="width:28px;height:28px;object-fit:contain;flex-shrink:0;">`:`<span data-icon="${m(j.icon||"layout")}"></span>`}<span class="dm-admin-sidebar-title">${m(j.title)}</span>${z}${We()}</div>`,O=r.some(c=>c&&c.type==="spacer"&&c.size==="flex")?" dm-admin-sidebar--has-flex-spacer":"",xe=`${U}<nav class="dm-admin-sidebar${I}${O}">${G}${r.map(c=>ue(c,B)).join("")}</nav>`,Q=document.createRange();Q.selectNodeContents(s);const $e=Q.createContextualFragment(xe);s.replaceChildren($e),Domma.icons.scan(s),s.querySelector(".dm-admin-sidebar-arrange")?.addEventListener("click",()=>ze(s));for(const c of L)X.set(c.url,B[c.url]??null);ce(s),ft(s),A.refresh.then(c=>{if(!(!A.isActive()||!s.isConnected))for(const[x,p]of Object.entries(c))V(s,x,p)}),s.removeAttribute("data-menu-hover"),s.removeAttribute("data-menu-active"),s.style.removeProperty("--dm-menu-highlight"),Be(s,{highlight:v});const T=document.querySelector(".dashboard-wrapper");["position","top","bottom","left","right","zIndex","maxHeight","transform"].forEach(c=>{s.style[c]=""}),s.classList.remove("dm-menu-floating"),T&&(T.style.paddingLeft="",T.style.paddingRight=""),s.__dmGutter=()=>{if(l!=="floating"||!T)return;const c=h&&h.anchor||"TL",x=Number.isFinite(Number(h?.offsetX))?Number(h.offsetX):16,p=s.classList.contains("is-collapsed")?Ge:parseInt(s.style.width,10)||s.getBoundingClientRect().width||260,N=Math.round(p+x*2)+"px";c==="R"||c==="TR"||c==="BR"?T.style.paddingRight=N:T.style.paddingLeft=N},l==="floating"&&je(s,{position:"floating",float:h||{}}),s.querySelectorAll("details").forEach(c=>{c.addEventListener("toggle",function(){const x=this.getAttribute("data-state-key");x&&S.set(x,this.open);const p=this.querySelector(":scope > .sidebar-children");if(p)if(this.open){const N=p.scrollHeight;p.style.maxHeight="0",requestAnimationFrame(()=>{p.style.maxHeight=N+"px",p.addEventListener("transitionend",function we(){p.style.maxHeight="none",p.removeEventListener("transitionend",we)})})}else{const N=p.scrollHeight;p.style.maxHeight=N+"px",requestAnimationFrame(()=>{p.style.maxHeight="0"})}})}),De(s),gt(s),s.dataset.routeWired||(s.dataset.routeWired="1",M.subscribe("router:afterChange",()=>s.__dmSetActive?.()));function ee(){const c=/^#\/(dashboards|my-dashboard)\b/.test(location.hash)?"#/":location.hash||"#/";$(s).find(".sidebar-link").removeClass("active"),$(s).find(`.sidebar-link[data-url="${c}"]`).addClass("active")}ee(),s.__dmSetActive=ee}function ge(){try{document.dispatchEvent(new CustomEvent("dm:sidebar-rebuild"))}catch{}}function J(e){return`${location.origin}${location.pathname}${e}`}function _(e,n){const t=w(),o=e(K(t));if(!Qe(t,o)){E.toast("Frequent could not be saved - this browser is blocking storage.",{type:"error"});return}o.length&&re(t,!1),be(t),n&&E.toast(n,{type:"success",duration:2500}),ge()}function be(e){Je(g,e).catch(()=>E.toast("Frequent is saved in this browser only - the server could not keep it.",{type:"warning"}))}function ht(e){if(!re(w(),e)){E.toast("Frequent could not be saved - this browser is blocking storage.",{type:"error"});return}be(w()),E.toast(e?"Frequent hidden. Add a Tool to it from its right-click menu to bring it back.":"Frequent shown.",{type:"success",duration:3500}),ge()}function ye(e){const n=e.dataset.url;if(!n)return[];const t=e.querySelector(".sidebar-text")?.textContent.trim()||n,o=K(w()),s=o.includes(n),a=!!e.closest("[data-frequent]"),u=o.indexOf(n),i=!s&&o.length>=oe;return[{header:t},{label:"Open",icon:"arrow-right",...ae(n)&&{shortcut:nt(ae(n))},action:()=>{location.hash=n}},{label:"Open in a new tab",icon:"external-link",action:()=>window.open(J(n),"_blank","noopener")},{type:"divider"},s?{label:"Remove from Frequent",icon:"star",action:()=>_(r=>Ze(r,n),`${t} removed from Frequent.`)}:{label:i?`Frequent is full (${oe})`:"Add to Frequent",icon:"star",disabled:i,action:()=>_(r=>Ke(r,n),`${t} added to Frequent.`)},...a?[{label:"Move up",icon:"arrow-up",disabled:u<=0,action:()=>_(r=>ie(r,n,-1))},{label:"Move down",icon:"arrow-down",disabled:u<0||u>=o.length-1,action:()=>_(r=>ie(r,n,1))}]:[],{type:"divider"},{label:"Copy link",icon:"link",action:async()=>{try{await navigator.clipboard.writeText(J(n)),E.toast("Link copied.",{type:"success",duration:2e3})}catch{E.toast(J(n),{type:"info",duration:8e3})}}}]}function ve(e,n,t={}){const o=e.closest("details, [data-frequent]"),s=t.isOpen||(()=>!!o?.open),a=t.setOpen||(r=>{o&&(o.open=r)}),u=e.querySelector(".sidebar-text")?.textContent.trim()||"Folder",i=t.setAll||(r=>n.querySelectorAll("details").forEach(d=>{d.open!==r&&(d.open=r)}));return[{header:u},{label:s()?"Close":"Open",icon:s()?"chevron-up":"chevron-down",action:()=>a(!s())},...t.singleBranch?[]:[{label:"Open every folder",icon:"chevrons-down",action:()=>i(!0)}],{label:t.singleBranch?"Close all menus":"Close every folder",icon:"chevrons-up",action:()=>i(!1)},...o?.hasAttribute("data-frequent-empty")?[{type:"divider"},{label:"Hide Frequent until I add a Tool",icon:"eye-off",action:()=>ht(!0)}]:o?.hasAttribute("data-frequent")?[{type:"divider"},{label:"Clear Frequent",icon:"x",action:()=>_(()=>[],"Frequent cleared.")}]:[]]}function gt(e){e.dataset.ctxWired||typeof E>"u"||!E.contextMenu||(e.dataset.ctxWired="1",E.contextMenu(e,{match:"a.sidebar-link, details > summary",inherit:!1,density:"compact",items:n=>n.matches("summary")?ve(n,e):ye(n)}))}
|
|
7
|
+
</details>`}return`<a href="${i}" class="sidebar-link" data-url="${i}"${v}${f}${q}>${r} <span class="sidebar-text">${u}</span>${rt(e,n)}${F}</a>`}async function ct(e=[],n=[],t=null){const o=y=>y.then(C=>Array.isArray(C)?C.length:Array.isArray(C?.entries)?C.entries.length:0).catch(()=>0),s=fe(e,t),r=new Set(s.map(y=>y.url)),u={pages:"#/pages",media:"#/media",menus:"#/menus","context-menus":"#/context-menus",theme:"#/theme",collections:"#/collections",forms:"#/forms",views:"#/views",actions:"#/actions",blocks:"#/blocks",components:"#/components",users:"#/users",plugins:"#/plugins",notifications:"#/system/notifications"},i=(y,C)=>W(n,y)&&!r.has(u[y])?C():Promise.resolve(0),[a,d,l,f,h,v,k,q,F,b,A,B,L,I]=await Promise.all([i("pages",()=>o(g.pages.list())),i("media",()=>o(g.media.list())),i("menus",()=>o(g.menus.list())),i("context-menus",()=>o(g.contextMenus.list())),i("theme",()=>g.theme.get().then(y=>(y?.overrides||[]).length).catch(()=>0)),i("collections",()=>o(g.collections.list())),i("forms",()=>o(g.forms.list())),i("views",()=>o(g.views.list())),i("actions",()=>o(g.actions.list())),i("blocks",()=>o(g.blocks.list())),i("components",()=>o(g.components.list())),i("users",()=>o(g.users.list())),i("plugins",()=>o(g.plugins.list())),i("notifications",()=>g.system?.notifications?.unreadCount?.().then(y=>y?.count??0).catch(()=>0)??Promise.resolve(0))]),P=await pe(s),U={"#/pages":a,"#/media":d,"#/menus":l,"#/context-menus":f,"#/theme":h,"#/collections":v,"#/forms":k,"#/views":q,"#/actions":F,"#/blocks":b,"#/components":A,"#/users":B,"#/plugins":L,"#/system/notifications":I},z={};for(const[y,C]of Object.entries(U)){if(r.has(y))continue;const[G,Y]=lt[y]??["item","items"],O=D(C);O&&(z[y]={...O,label:`${O.count} ${O.count===1?G:Y}`})}return{...z,...P}}let j=[];const dt="dm:sidebar-count",ut=6e4,ft=qe(4);function fe(e=[],n=null){const t=[],o=s=>{if(s&&!(n&&typeof s.url=="string"&&s.url.startsWith("#/")&&!Le(n,s.url.slice(1)))){s.url&&typeof s.countUrl=="string"&&s.countUrl&&t.push({url:s.url,countUrl:s.countUrl,text:s.text||"",countLabel:s.countLabel||"",inventory:s.inventory===!0});for(const r of[...s.children||[],...s.items||[]])o(r)}};for(const s of e??[])o(s?.item??s);return t}async function me(e,{priority:n=!1}={}){try{const t=new Date,o=`${t.getFullYear()}-${String(t.getMonth()+1).padStart(2,"0")}-${String(t.getDate()).padStart(2,"0")}`,s=e.countUrl+(e.countUrl.includes("?")?"&":"?")+`today=${o}`,r=w()?.id,u=await ft(()=>!ke()||w()?.id!==r?null:H.get(s,{priority:"low"}),{key:JSON.stringify([r,s]),priority:n});return D(u,{fallbackLabel:e.countLabel||e.text})}catch{return null}}function V(e,n,t){Fe(n,t),t?.notify&&je(w()?.id??null,n,t.notify);const o=[...e.querySelectorAll("a.sidebar-link")].filter(s=>s.dataset.url===n);if(o.length){for(const s of o){const r=s.querySelector(".sidebar-badge[data-count-for]");if(!t||!(t.count>0)&&!t.showZero)r?.remove();else{const u=document.createRange();u.selectNodeContents(s);const i=u.createContextualFragment(le(n,t)).firstElementChild;r?r.replaceWith(i):s.querySelector(".sidebar-text")?.after(i)}}X.set(n,t),ce(e)}}const X=new Map;async function pe(e){const n=await Ce(e,{legacy:async t=>{const o=await Promise.all(t.map(async s=>[s.url,await me(s)]));return Object.fromEntries(o)}});return Object.fromEntries(e.map(t=>[t.url,n[t.url]==null?null:D(n[t.url],{fallbackLabel:t.countLabel||t.text})]))}async function Z(e,n=null){const t=w()?.id,o=j;try{const s=n?{[n]:await me(j.find(r=>r.url===n),{priority:!0})}:await pe(j);if(w()?.id!==t||j!==o)return;for(const[r,u]of Object.entries(s)){const i=X.get(r);JSON.stringify(i??null)!==JSON.stringify(u)&&V(e,r,u)}}catch{}}function mt(e){e.dataset.liveCounts||(e.dataset.liveCounts="1",window.addEventListener(dt,n=>{const t=n.detail||{};if(t.url)if(t.count!==void 0){const o=j.find(s=>s.url===t.url);V(e,t.url,D(t,{fallbackLabel:o?.countLabel||o?.text||""}))}else Z(e,t.url)}),setInterval(()=>{!document.hidden&&document.body.contains(e)&&Z(e)},ut),document.addEventListener("visibilitychange",()=>{document.hidden||Z(e)}),pt(e))}function he(e,n,t){const o=(u,i,a)=>{const d=document.createElement(u);return i&&(d.className=i),a!=null&&(d.textContent=String(a)),d};if(!t){e.textContent=n;return}e.appendChild(o("strong","dm-badge-pop-title",t.title||n)),(t.rows?.length||!t.items?.length)&&e.appendChild(o("p","dm-badge-pop-sub",n));const s=Array.isArray(t.rows)?t.rows.slice(0,6):[];if(s.length){const u=o("dl","dm-badge-pop-rows");for(const i of s)u.appendChild(o("dt",null,i.label)),u.appendChild(o("dd",i.tone==="danger"?"is-danger":null,i.value));e.appendChild(u)}const r=Array.isArray(t.items)?t.items.slice(0,6):[];if(r.length){const u=o("ul","dm-badge-pop-items");for(const i of r){const a=o("li",i.tone==="danger"?"is-danger":null);a.appendChild(o("span","dm-badge-pop-text",i.text)),i.meta&&a.appendChild(o("span","dm-badge-pop-meta",i.meta)),u.appendChild(a)}e.appendChild(u)}else t.empty&&e.appendChild(o("p","dm-badge-pop-empty",t.empty))}function pt(e){let n=null,t=null;const o=()=>e.classList.contains("is-collapsed"),s=()=>{clearTimeout(t),n?.remove(),n=null},r=(d,l)=>{s(),t=setTimeout(()=>{n=document.createElement("div"),n.className="dm-help-pop dm-badge-pop",n.setAttribute("role","tooltip"),l(n),document.body.appendChild(n);const f=d.getBoundingClientRect(),h=n.getBoundingClientRect(),v=Math.min(f.right+10,innerWidth-h.width-8);n.style.left=`${Math.max(8,v)}px`,n.style.top=`${Math.max(8,Math.min(f.top+f.height/2-h.height/2,innerHeight-h.height-8))}px`},150)},u=d=>{const l=d?.dataset.badgeLabel;if(!l)return s();r(d,f=>he(f,l,R.get(d.dataset.countFor)))},i=d=>{const l=d.querySelector(":scope > .sidebar-text")?.textContent.trim()||"",f=d.querySelector(":scope > .sidebar-badge[data-badge-label]:not([hidden])");r(d,h=>{const v=f&&R.get(f.dataset.countFor);if(v)return he(h,f.dataset.badgeLabel,{...v,title:v.title||l});if(!f){h.textContent=l;return}const k=document.createElement("strong");k.className="dm-badge-pop-title",k.textContent=l;const q=document.createElement("p");q.className="dm-badge-pop-sub",q.textContent=f.dataset.badgeLabel,h.append(k,q)})},a=d=>{const l=d.closest?.("a.sidebar-link, summary");return l&&e.contains(l)?l:null};e.addEventListener("mouseover",d=>{if(o()){const f=a(d.target);f&&!f.contains(d.relatedTarget)&&i(f);return}const l=d.target.closest?.(".sidebar-badge[data-badge-label]");l&&u(l)}),e.addEventListener("mouseout",d=>{if(o()){const l=a(d.target);l&&!l.contains(d.relatedTarget)&&s();return}d.target.closest?.(".sidebar-badge[data-badge-label]")&&s()}),e.addEventListener("focusin",d=>{const l=d.target.closest?.("a, summary");if(o()&&l&&e.contains(l)&&l.querySelector(":scope > .sidebar-text"))return i(l);const f=l?.querySelector(":scope > .sidebar-badge[data-badge-label]:not([hidden])");f?u(f):s()}),e.addEventListener("focusout",s),e.addEventListener("click",s),window.addEventListener("scroll",s,!0),window.addEventListener("hashchange",s)}async function ht(e=[],n=null){if(n?.title||n?.enabled===!1)return n;if(!W(e,"settings"))return{title:"Domma CMS"};try{const t=await g.settings.get();return{...t?.adminBrand,title:t?.adminBrand?.title||t?.title||"Domma CMS"}}catch{return{title:"Domma CMS"}}}export async function renderAdminSidebar({mount:e,permissions:n,adminScope:t=null,brand:o=null}){const s=$(e).get(0);if(!s)return;const r=Qe(g,w()).catch(()=>{}),u=Promise.resolve().then(()=>H.get("/api/sidebar/registered-items")).catch(()=>[]),i=it(n);let a,d=null,l=null,f=null,h=null,v=null;try{const c=await g.get("/sidebar/menu");a=Array.isArray(c?.items)?c.items:null,d=c?.variant||null,l=c?.position||null,f=c?.style||null,h=c?.float||null,v=c?.highlight||null}catch{a=null}(!a||!a.some(c=>c&&(!c.hidden||c.restricted)))&&(console.warn("[admin-sidebar] No admin-sidebar menu found; using fallback tree"),a=ot.slice());let k=[];try{k=await u||[]}catch{}a=Re(a,se),a=Te(a,k.filter(c=>(c?.item??c)?.url!==se)),a=Ne(a),a=Ie(a,[He()]);const q=await i;q.length&&at(a,q),a=_e(a,n,t),a=Pe(a),await r;const F=Xe(a,K(w()),{hidden:Ze(w())});F&&(a=[F,...a]),nt(a);const b=k.filter(c=>W(n,c?.item?.permission));j=fe(b,t);const A=Ae({user:w(),permissions:n,adminScope:t,registered:b,tree:a,actions:{linkItems:ye,folderItems:ve},fetcher:()=>ct(b,n,t)}),B=A.counts,L=await ht(n,o),I=d?` dm-admin-sidebar--${m(d)}`:"";function P(c,x="px"){if(c==null)return c;const p=String(c).trim();return/^\d+(\.\d+)?$/.test(p)?`${p}${x}`:p}let U="";if(f){const c=[];f.fontFamily&&c.push(`font-family: ${f.fontFamily}, sans-serif`),f.fontSize&&c.push(`font-size: ${P(f.fontSize)}`),f.fontWeight&&c.push(`font-weight: ${f.fontWeight}`),f.letterSpacing&&c.push(`letter-spacing: ${P(f.letterSpacing,"em")}`);const x=[];if(c.length&&x.push(`#admin-sidebar .dm-admin-sidebar, #admin-sidebar .dm-admin-sidebar .sidebar-link, #admin-sidebar .dm-admin-sidebar summary, #admin-sidebar .dm-admin-sidebar .sidebar-text { ${c.join("; ")} }`),f.iconSize){const p=P(f.iconSize);x.push(`#admin-sidebar .dm-admin-sidebar [data-icon], #admin-sidebar .dm-admin-sidebar [data-icon] svg { width: ${p} !important; height: ${p} !important; }`)}x.length&&(U=`<style data-admin-sidebar-style>${x.join(" ")}</style>`)}const z=ze(n)?Ue():"",y=String(L.logo||"").trim(),C=/^(?:\/(?!\/)|https?:\/\/)/i.test(y)?y:"",G=C?`<img src="${m(C)}" alt="" style="width:28px;height:28px;object-fit:contain;flex-shrink:0;">`:`<span data-icon="${m(L.icon||"layout")}"></span>`,O=`<div class="dm-admin-sidebar-header">${L.enabled===!1?"":`${G}<span class="dm-admin-sidebar-title">${m(L.title)}</span>`}${z}${De()}</div>`,xe=a.some(c=>c&&c.type==="spacer"&&c.size==="flex")?" dm-admin-sidebar--has-flex-spacer":"",$e=`${U}<nav class="dm-admin-sidebar${I}${xe}">${O}${a.map(c=>ue(c,B)).join("")}</nav>`,Q=document.createRange();Q.selectNodeContents(s);const we=Q.createContextualFragment($e);s.replaceChildren(we),Domma.icons.scan(s),s.querySelector(".dm-admin-sidebar-arrange")?.addEventListener("click",()=>We(s));for(const c of j)X.set(c.url,B[c.url]??null);ce(s),mt(s),A.refresh.then(c=>{if(!(!A.isActive()||!s.isConnected))for(const[x,p]of Object.entries(c))V(s,x,p)}),s.removeAttribute("data-menu-hover"),s.removeAttribute("data-menu-active"),s.style.removeProperty("--dm-menu-highlight"),Oe(s,{highlight:v});const T=document.querySelector(".dashboard-wrapper");["position","top","bottom","left","right","zIndex","maxHeight","transform"].forEach(c=>{s.style[c]=""}),s.classList.remove("dm-menu-floating"),T&&(T.style.paddingLeft="",T.style.paddingRight=""),s.__dmGutter=()=>{if(l!=="floating"||!T)return;const c=h&&h.anchor||"TL",x=Number.isFinite(Number(h?.offsetX))?Number(h.offsetX):16,p=s.classList.contains("is-collapsed")?Ke:parseInt(s.style.width,10)||s.getBoundingClientRect().width||260,N=Math.round(p+x*2)+"px";c==="R"||c==="TR"||c==="BR"?T.style.paddingRight=N:T.style.paddingLeft=N},l==="floating"&&Be(s,{position:"floating",float:h||{}}),s.querySelectorAll("details").forEach(c=>{c.addEventListener("toggle",function(){const x=this.getAttribute("data-state-key");x&&S.set(x,this.open);const p=this.querySelector(":scope > .sidebar-children");if(p)if(this.open){const N=p.scrollHeight;p.style.maxHeight="0",requestAnimationFrame(()=>{p.style.maxHeight=N+"px",p.addEventListener("transitionend",function Se(){p.style.maxHeight="none",p.removeEventListener("transitionend",Se)})})}else{const N=p.scrollHeight;p.style.maxHeight=N+"px",requestAnimationFrame(()=>{p.style.maxHeight="0"})}})}),Ge(s),bt(s),s.dataset.routeWired||(s.dataset.routeWired="1",M.subscribe("router:afterChange",()=>s.__dmSetActive?.()));function ee(){const c=/^#\/(dashboards|my-dashboard)\b/.test(location.hash)?"#/":location.hash||"#/";$(s).find(".sidebar-link").removeClass("active"),$(s).find(`.sidebar-link[data-url="${c}"]`).addClass("active")}ee(),s.__dmSetActive=ee}function ge(){try{document.dispatchEvent(new CustomEvent("dm:sidebar-rebuild"))}catch{}}function J(e){return`${location.origin}${location.pathname}${e}`}function _(e,n){const t=w(),o=e(K(t));if(!et(t,o)){E.toast("Frequent could not be saved - this browser is blocking storage.",{type:"error"});return}o.length&&ae(t,!1),be(t),n&&E.toast(n,{type:"success",duration:2500}),ge()}function be(e){Ye(g,e).catch(()=>E.toast("Frequent is saved in this browser only - the server could not keep it.",{type:"warning"}))}function gt(e){if(!ae(w(),e)){E.toast("Frequent could not be saved - this browser is blocking storage.",{type:"error"});return}be(w()),E.toast(e?"Frequent hidden. Add a Tool to it from its right-click menu to bring it back.":"Frequent shown.",{type:"success",duration:3500}),ge()}function ye(e){const n=e.dataset.url;if(!n)return[];const t=e.querySelector(".sidebar-text")?.textContent.trim()||n,o=K(w()),s=o.includes(n),r=!!e.closest("[data-frequent]"),u=o.indexOf(n),i=!s&&o.length>=oe;return[{header:t},{label:"Open",icon:"arrow-right",...re(n)&&{shortcut:st(re(n))},action:()=>{location.hash=n}},{label:"Open in a new tab",icon:"external-link",action:()=>window.open(J(n),"_blank","noopener")},{type:"divider"},s?{label:"Remove from Frequent",icon:"star",action:()=>_(a=>Je(a,n),`${t} removed from Frequent.`)}:{label:i?`Frequent is full (${oe})`:"Add to Frequent",icon:"star",disabled:i,action:()=>_(a=>Ve(a,n),`${t} added to Frequent.`)},...r?[{label:"Move up",icon:"arrow-up",disabled:u<=0,action:()=>_(a=>ie(a,n,-1))},{label:"Move down",icon:"arrow-down",disabled:u<0||u>=o.length-1,action:()=>_(a=>ie(a,n,1))}]:[],{type:"divider"},{label:"Copy link",icon:"link",action:async()=>{try{await navigator.clipboard.writeText(J(n)),E.toast("Link copied.",{type:"success",duration:2e3})}catch{E.toast(J(n),{type:"info",duration:8e3})}}}]}function ve(e,n,t={}){const o=e.closest("details, [data-frequent]"),s=t.isOpen||(()=>!!o?.open),r=t.setOpen||(a=>{o&&(o.open=a)}),u=e.querySelector(".sidebar-text")?.textContent.trim()||"Folder",i=t.setAll||(a=>n.querySelectorAll("details").forEach(d=>{d.open!==a&&(d.open=a)}));return[{header:u},{label:s()?"Close":"Open",icon:s()?"chevron-up":"chevron-down",action:()=>r(!s())},...t.singleBranch?[]:[{label:"Open every folder",icon:"chevrons-down",action:()=>i(!0)}],{label:t.singleBranch?"Close all menus":"Close every folder",icon:"chevrons-up",action:()=>i(!1)},...o?.hasAttribute("data-frequent-empty")?[{type:"divider"},{label:"Hide Frequent until I add a Tool",icon:"eye-off",action:()=>gt(!0)}]:o?.hasAttribute("data-frequent")?[{type:"divider"},{label:"Clear Frequent",icon:"x",action:()=>_(()=>[],"Frequent cleared.")}]:[]]}function bt(e){e.dataset.ctxWired||typeof E>"u"||!E.contextMenu||(e.dataset.ctxWired="1",E.contextMenu(e,{match:"a.sidebar-link, details > summary",inherit:!1,density:"compact",items:n=>n.matches("summary")?ve(n,e):ye(n)}))}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export function siteSearchSection(a){const r=a.querySelector("[data-site-search-tab]"),n=a.querySelector("[data-site-search-panel]"),h=a.querySelector("[data-site-search-dirty]");let s=null,d=!1,i=!1;const f=[];function u(e){for(const t of e.querySelectorAll("button, a")){if(t.dataset.siteTooltip||t.classList.contains("dm-help-btn")||!t.querySelector("svg, [data-icon]"))continue;const o=t.getAttribute("aria-label")||t.title||t.textContent.trim();if(!o)continue;t.dataset.siteTooltip="1",t.dataset.tooltip=o,t.setAttribute("aria-label",o);const c=E.tooltip(t,{content:o,placement:"bottom"});f.push(()=>{for(const l of Array.isArray(c)?c:[c])l?.destroy?.()})}}async function p(){if(!(d||s||i)){d=!0,n.textContent="Loading Search settings...";try{const[e,t]=await Promise.all([fetch("/admin/js/templates/search.html",{cache:"no-cache"}),import("../views/search.js")]);if(!e.ok)throw new Error("Could not load Search settings.");const o=await e.text();if(i||!a.isConnected)return;const c=document.createElement("template");c.innerHTML=o,n.replaceChildren(c.content.cloneNode(!0)),await t.mountSearchSettings($(n),{onDirty:l=>{i||(h.hidden=!l)},onReady:l=>{i?l.dispose():(s=l,u(n))}}),i||u(n)}catch(e){i||(n.textContent=e.message||"Could not load Search settings. Reopen Site Settings to try again.",n.setAttribute("role","alert"))}finally{d=!1}}}return r.addEventListener("click",p),u(a.closest(".dm-plugin-shell")||a),r.classList.contains("active")&&p(),{dirty:()=>s?.dirty()||!1,save:()=>s?.save(),discard:()=>s?.discard(),dispose(){i=!0,r.removeEventListener("click",p),s?.dispose(),f.splice(0).forEach(e=>e())}}}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export const TAB_FIELDS={general:["title","tagline","siteUrl","adminHome","spacerSize","bodyClass"],branding:["brandText","brandLogo","brandIcon","brandUrl","adminBrandTitle","adminBrandLogo","adminBrandIcon"],favicon:["favicon"],
|
|
2
|
-
`):"",footerTagline:o(t.footer?.tagline),footerEmail:o(t.footer?.email),footerAddress:o(t.footer?.address),footerGradFrom:o(t.footer?.gradient?.from),footerGradTo:o(t.footer?.gradient?.to),socialTwitter:o(t.social?.twitter),socialFacebook:o(t.social?.facebook),socialInstagram:o(t.social?.instagram),socialLinkedin:o(t.social?.linkedin),socialGithub:o(t.social?.github),socialYoutube:o(t.social?.youtube),smtpHost:o(t.smtp?.host),smtpPort:o(t.smtp?.port||587),smtpUser:o(t.smtp?.user),smtpPass:"",smtpSecure:t.smtp?.secure===!0,smtpInsecureTLS:t.smtp?.allowInsecureTLS===!0,smtpFromAddress:o(t.smtp?.fromAddress),smtpFromName:o(t.smtp?.fromName),bttEnabled:r.enabled!==!1,bttSmooth:r.smooth!==!1,bttLabel:o(r.label),bttThreshold:o(r.scrollThreshold??300),bttPosition:o(r.position,"bottom-right"),bttOffset:o(r.offset??32),bttBottomOffset:o(r.bottomOffset??r.offset??32),gtbEnabled:
|
|
3
|
-
`).map(
|
|
1
|
+
export const TAB_FIELDS={general:["title","tagline","siteUrl","adminHome","spacerSize","bodyClass"],branding:["brandEnabled","brandText","brandLogo","brandIcon","brandUrl","adminBrandEnabled","adminBrandTitle","adminBrandLogo","adminBrandIcon"],favicon:["favicon"],footer:["footerCopyright","footerLayout","footerBlurb","footerBlocks","footerClass","footerSocialClass","footerLook","footerCtaText","footerCtaHighlight","footerCtaUrl","footerCtaSub","footerWatermark","footerStatus","footerFacts","footerTagline","footerEmail","footerAddress","footerGradFrom","footerGradTo","socialTwitter","socialFacebook","socialInstagram","socialLinkedin","socialGithub","socialYoutube"],email:["smtpHost","smtpPort","smtpUser","smtpPass","smtpSecure","smtpInsecureTLS","smtpFromAddress","smtpFromName"],pageNavigation:["bttEnabled","bttSmooth","bttLabel","bttThreshold","bttPosition","bttOffset","bttBottomOffset","gtbEnabled","gtbSmooth","gtbLabel","gtbThreshold","gtbPosition","gtbOffset","gtbBottomOffset"],cookies:["ccEnabled","ccMessage","ccAcceptAll","ccRejectAll","ccCustomize","ccSavePrefs","ccPrivacyText","ccPrivacyUrl","ccCookieText","ccCookieUrl","ccPosition","ccLayout","ccTheme","ccShowFunctional","ccShowAnalytics","ccShowMarketing","ccVersion","ccSettingsLink","ccSettingsLinkText"],breadcrumbs:["bcEnabled","bcHomeLabel","bcPosition","bcOffsetX","bcOffsetY"],motion:["mtEnabled","mtLabel","mtDefault","mtControl","mtPlacement","mtTriggerStyle","mtPosition","mtOffsetX","mtOffsetY","mtIconSize","mtIconRadius","mtClass"]},SECRET_FIELDS=["smtpPass"],BREADCRUMB_POSITIONS=["TL","TR","BL","BR"],MOTION_PLACEMENTS=["footer","floating","navbar"],MOTION_LABEL="Reduce motion";const d=["system","off","on"],c=(t,r,n,e)=>{const a=Number.parseInt(t,10);return Number.isFinite(a)?Math.min(e,Math.max(n,a)):r};export function motionFromSettings(t={}){const r=t.motion&&typeof t.motion=="object"?t.motion:{},n=t.footer||{},e=b=>r[b]!==void 0&&r[b]!==null,a=e("default")?r.default:n.motionDefault,i=e("control")?r.control:n.motionControl,m=c(r.iconSize,52,32,96);return{mtEnabled:(e("enabled")?r.enabled:n.showMotionToggle)!==!1,mtLabel:typeof r.label=="string"&&r.label.trim()?r.label.trim():MOTION_LABEL,mtDefault:d.includes(a)?a:"system",mtControl:i==="checkbox"?"checkbox":"switch",mtPlacement:MOTION_PLACEMENTS.includes(r.placement)?r.placement:"footer",mtTriggerStyle:r.triggerStyle==="icon-text"?"icon-text":"icon",mtPosition:BREADCRUMB_POSITIONS.includes(r.position)?r.position:"BL",mtOffsetX:o(c(r.offsetX,32,0,500)),mtOffsetY:o(c(r.offsetY,32,0,500)),mtIconSize:o(m),mtIconRadius:o(c(r.iconRadius,26,0,Math.floor(m/2))),mtClass:o(r.class)}}export function motionFromForm(t){const r=c(t.mtIconSize,52,32,96);return{enabled:t.mtEnabled===!0,default:d.includes(t.mtDefault)?t.mtDefault:"system",placement:MOTION_PLACEMENTS.includes(t.mtPlacement)?t.mtPlacement:"footer",control:t.mtControl==="checkbox"?"checkbox":"switch",triggerStyle:t.mtTriggerStyle==="icon-text"?"icon-text":"icon",label:o(t.mtLabel).trim().replace(/\s+/g," ").slice(0,40)||MOTION_LABEL,position:BREADCRUMB_POSITIONS.includes(t.mtPosition)?t.mtPosition:"BL",offsetX:c(t.mtOffsetX,32,0,500),offsetY:c(t.mtOffsetY,32,0,500),iconSize:r,iconRadius:c(t.mtIconRadius,26,0,Math.floor(r/2)),class:o(t.mtClass).trim()}}export function footerBlockNames(t){return[...new Set(String(t??"").split(/[\s,]+/).map(r=>r.trim().toLowerCase()).filter(Boolean))]}export function gaIdError(t){const r=o(t).trim();return!r||/^G-[A-Z0-9]{4,20}$/i.test(r)?"":"A measurement ID looks like G-XXXXXXXXXX (Google Analytics > Admin > Data streams)."}export const FOOTER_LAYOUTS=["auto","columns","row"],FOOTER_LOOKS=["","showcase"],FOOTER_FACTS_MAX=4;const u=/^(\/|#|https?:\/\/|mailto:)/,f=/^#[0-9a-f]{6}$/i;export function footerShowcaseErrors(t){const r=[],n=o(t.footerCtaUrl).trim();n&&!u.test(n)&&r.push("The footer call to action link must start with /, #, https:// or mailto: (Footer tab).");for(const[e,a]of[["from",t.footerGradFrom],["to",t.footerGradTo]]){const i=o(a).trim();i&&!f.test(i)&&r.push(`The footer gradient "${e}" colour must look like #f26b3a (Footer tab).`)}return r}const o=(t,r="")=>t==null?r:String(t),s=(t,r)=>{const n=Number.parseInt(t,10);return Number.isFinite(n)?n:r};export function homeValue(t){return/^\/[A-Za-z0-9\-_/]*$/.test(t||"")&&String(t).replace(/\/+$/,"")||"/"}export function formFromSettings(t={}){const r=t.backToTop||{},n=t.goToBottom||{},e=t.cookieConsent||{},a=t.breadcrumbs||{};return{title:o(t.title),tagline:o(t.tagline),siteUrl:o(t.baseUrl),adminHome:homeValue(t.adminHome),spacerSize:o(t.layoutOptions?.spacerSize??40),bodyClass:o(t.bodyClass),brandEnabled:t.brand?.enabled!==!1,brandText:o(t.brand?.text),brandLogo:o(t.brand?.logo),brandIcon:o(t.brand?.icon),brandUrl:o(t.brand?.url,"/"),favicon:o(t.favicon),adminBrandEnabled:t.adminBrand?.enabled!==!1,adminBrandTitle:o(t.adminBrand?.title),adminBrandLogo:o(t.adminBrand?.logo),adminBrandIcon:o(t.adminBrand?.icon),footerCopyright:o(t.footer?.copyright),footerLayout:FOOTER_LAYOUTS.includes(t.footer?.layout)?t.footer.layout:"auto",footerBlurb:o(t.footer?.blurb),footerBlocks:Array.isArray(t.footer?.blocks)?t.footer.blocks.filter(i=>typeof i=="string").join(", "):"",footerClass:o(t.footer?.class),footerSocialClass:o(t.footer?.socialClass),footerLook:FOOTER_LOOKS.includes(t.footer?.look)?t.footer.look:"",footerCtaText:o(t.footer?.cta?.text),footerCtaHighlight:o(t.footer?.cta?.highlight),footerCtaUrl:o(t.footer?.cta?.url),footerCtaSub:o(t.footer?.cta?.sub),footerWatermark:o(t.footer?.watermark),footerStatus:o(t.footer?.status),footerFacts:Array.isArray(t.footer?.facts)?t.footer.facts.filter(i=>typeof i=="string").join(`
|
|
2
|
+
`):"",footerTagline:o(t.footer?.tagline),footerEmail:o(t.footer?.email),footerAddress:o(t.footer?.address),footerGradFrom:o(t.footer?.gradient?.from),footerGradTo:o(t.footer?.gradient?.to),socialTwitter:o(t.social?.twitter),socialFacebook:o(t.social?.facebook),socialInstagram:o(t.social?.instagram),socialLinkedin:o(t.social?.linkedin),socialGithub:o(t.social?.github),socialYoutube:o(t.social?.youtube),smtpHost:o(t.smtp?.host),smtpPort:o(t.smtp?.port||587),smtpUser:o(t.smtp?.user),smtpPass:"",smtpSecure:t.smtp?.secure===!0,smtpInsecureTLS:t.smtp?.allowInsecureTLS===!0,smtpFromAddress:o(t.smtp?.fromAddress),smtpFromName:o(t.smtp?.fromName),bttEnabled:r.enabled!==!1,bttSmooth:r.smooth!==!1,bttLabel:o(r.label),bttThreshold:o(r.scrollThreshold??300),bttPosition:o(r.position,"bottom-right"),bttOffset:o(r.offset??32),bttBottomOffset:o(r.bottomOffset??r.offset??32),gtbEnabled:n.enabled===!0,gtbSmooth:n.smooth!==!1,gtbLabel:o(n.label),gtbThreshold:o(n.scrollThreshold??300),gtbPosition:o(n.position,"bottom-right"),gtbOffset:o(n.offset??32),gtbBottomOffset:o(n.bottomOffset??n.offset??32),ccEnabled:e.enabled!==!1,ccMessage:o(e.message),ccAcceptAll:o(e.acceptAllText,"Accept All"),ccRejectAll:o(e.rejectAllText,"Reject All"),ccCustomize:o(e.customizeText,"Customize"),ccSavePrefs:o(e.savePreferencesText,"Save Preferences"),ccPrivacyText:o(e.privacyPolicyText,"Privacy Policy"),ccPrivacyUrl:o(e.privacyPolicyUrl),ccCookieText:o(e.cookiePolicyText,"Cookie Policy"),ccCookieUrl:o(e.cookiePolicyUrl),ccPosition:o(e.position,"bottom"),ccLayout:o(e.layout,"bar"),ccTheme:o(e.theme,"dark"),ccShowFunctional:e.showFunctional!==!1,ccShowAnalytics:e.showAnalytics!==!1,ccShowMarketing:e.showMarketing!==!1,ccVersion:o(e.consentVersion,"1.0"),ccSettingsLink:e.showSettingsLink!==!1,ccSettingsLinkText:o(e.settingsLinkText,"Cookie settings"),bcEnabled:a.enabled===!0,bcHomeLabel:o(a.homeLabel,"Home"),bcPosition:BREADCRUMB_POSITIONS.includes(a.position)?a.position:"TL",bcOffsetX:o(a.offsetX??8),bcOffsetY:o(a.offsetY??8),...motionFromSettings(t)}}export function payloadFromForm(t,r={},{adminHomeReady:n=!0}={}){const e=i=>o(i).trim(),a=motionFromForm(t);return{title:e(t.title),tagline:e(t.tagline),baseUrl:e(t.siteUrl).replace(/\/+$/,""),...n&&{adminHome:e(t.adminHome)||"/"},layoutOptions:{spacerSize:s(t.spacerSize,40)||40},bodyClass:e(t.bodyClass),brand:{...r.brand,enabled:t.brandEnabled!==!1,text:e(t.brandText),logo:e(t.brandLogo),icon:e(t.brandIcon),url:e(t.brandUrl)||"/"},favicon:e(t.favicon),adminBrand:{...r.adminBrand,enabled:t.adminBrandEnabled!==!1,title:e(t.adminBrandTitle),logo:e(t.adminBrandLogo),icon:e(t.adminBrandIcon)},footer:{copyright:e(t.footerCopyright),layout:FOOTER_LAYOUTS.includes(t.footerLayout)?t.footerLayout:"auto",blurb:e(t.footerBlurb),blocks:footerBlockNames(t.footerBlocks),links:r.footer?.links||[],showMotionToggle:a.enabled,motionDefault:a.default,motionControl:a.control,class:e(t.footerClass),socialClass:e(t.footerSocialClass),look:FOOTER_LOOKS.includes(t.footerLook)?t.footerLook:"",cta:{text:e(t.footerCtaText),highlight:e(t.footerCtaHighlight),url:e(t.footerCtaUrl),sub:e(t.footerCtaSub)},watermark:e(t.footerWatermark),status:e(t.footerStatus),facts:String(t.footerFacts??"").split(`
|
|
3
|
+
`).map(i=>i.trim()).filter(Boolean).slice(0,FOOTER_FACTS_MAX),tagline:e(t.footerTagline),email:e(t.footerEmail),address:e(t.footerAddress),gradient:{from:e(t.footerGradFrom),to:e(t.footerGradTo)}},social:{twitter:e(t.socialTwitter),facebook:e(t.socialFacebook),instagram:e(t.socialInstagram),linkedin:e(t.socialLinkedin),github:e(t.socialGithub),youtube:e(t.socialYoutube)},smtp:{host:e(t.smtpHost),port:s(t.smtpPort,587)||587,user:e(t.smtpUser),pass:o(t.smtpPass),secure:t.smtpSecure===!0,allowInsecureTLS:t.smtpInsecureTLS===!0,fromAddress:e(t.smtpFromAddress),fromName:e(t.smtpFromName)},backToTop:{enabled:t.bttEnabled===!0,scrollThreshold:s(t.bttThreshold,300),position:o(t.bttPosition)||"bottom-right",offset:s(t.bttOffset,32),bottomOffset:s(t.bttBottomOffset,32),label:e(t.bttLabel),smooth:t.bttSmooth===!0},goToBottom:{enabled:t.gtbEnabled===!0,scrollThreshold:s(t.gtbThreshold,300),position:o(t.gtbPosition)||"bottom-right",offset:s(t.gtbOffset,32),bottomOffset:s(t.gtbBottomOffset,32),label:e(t.gtbLabel),smooth:t.gtbSmooth===!0},cookieConsent:{enabled:t.ccEnabled===!0,message:e(t.ccMessage),acceptAllText:e(t.ccAcceptAll),rejectAllText:e(t.ccRejectAll),customizeText:e(t.ccCustomize),savePreferencesText:e(t.ccSavePrefs),privacyPolicyText:e(t.ccPrivacyText),privacyPolicyUrl:e(t.ccPrivacyUrl),cookiePolicyText:e(t.ccCookieText),cookiePolicyUrl:e(t.ccCookieUrl),position:o(t.ccPosition)||"bottom",layout:o(t.ccLayout)||"bar",theme:o(t.ccTheme)||"dark",showFunctional:t.ccShowFunctional===!0,showAnalytics:t.ccShowAnalytics===!0,showMarketing:t.ccShowMarketing===!0,consentVersion:e(t.ccVersion)||"1.0",showSettingsLink:t.ccSettingsLink===!0,settingsLinkText:e(t.ccSettingsLinkText)||"Cookie settings"},breadcrumbs:{enabled:t.bcEnabled===!0,homeLabel:e(t.bcHomeLabel)||"Home",position:BREADCRUMB_POSITIONS.includes(t.bcPosition)?t.bcPosition:"TL",offsetX:s(t.bcOffsetX,8),offsetY:s(t.bcOffsetY,8)},motion:a}}export function siteUrlError(t){const r=o(t).trim().replace(/\/+$/,"");return r?/^https?:\/\//i.test(r)?/^https?:\/\/([a-z0-9.\-]+|\[[0-9a-f:.]+\])(:\d{1,5})?$/i.test(r)?"":"Just the address, like https://example.com - no page path after it.":"Start with https:// (or http://).":""}const l=t=>typeof t=="boolean"?t:o(t);export function snapshot(t){return JSON.stringify(Object.keys(t).sort().map(r=>[r,l(t[r])]))}export function changedTabs(t,r){const n=[];for(const[e,a]of Object.entries(TAB_FIELDS))a.some(i=>l(t[i])!==l(r[i]))&&n.push(e);return n}export function draftOf(t){return Object.fromEntries(Object.entries(t).filter(([r])=>!SECRET_FIELDS.includes(r)))}const p={footerMotionToggle:"mtEnabled",footerMotionDefault:"mtDefault",footerMotionControl:"mtControl"};export function formFromDraft(t,r={}){const n={...r};for(const[a,i]of Object.entries(p))a in n&&!(i in n)&&(n[i]=n[a]),delete n[a];const e={...t};for(const[a,i]of Object.entries(n))a in e&&!SECRET_FIELDS.includes(a)&&(e[a]=i);return e}export function settingsProblems(t,r={}){const n=[];return o(t.smtpHost).trim()?o(t.smtpFromAddress).trim()||n.push({tone:"warn",tab:"email",text:"No From address - mail has no sender and many servers refuse it."}):n.push({tone:"danger",tab:"email",text:"No SMTP host - mail goes to a throwaway test inbox and reaches no one (password resets, form notifications)."}),(t.ccEnabled===!0||o(r.googleAnalytics?.measurementId).trim())&&!o(t.ccPrivacyUrl).trim()&&n.push({tone:"warn",tab:"cookies",text:"The cookie banner is on but links to no privacy policy."}),n}export function describeCacheKey(t){const r=/^page:(.*?):m\d+:role([^:]*):/.exec(String(t||""));return r?{path:r[1]||"/",role:r[2]||"anon",page:!0}:{path:String(t||""),role:"",page:!1}}export function expiresIn(t,r=Date.now()){if(t==null)return"no expiry";const n=t-r;return n<=0?"expired":n<6e4?`${Math.round(n/1e3)}s`:n<36e5?`${Math.round(n/6e4)}m`:`${Math.round(n/36e5)}h`}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
let s={};export function renderFrameworkVersions(r){r&&(s=r);for(const[e,i,o]of[["topbar-reactive-version","Reactive",s.reactive],["topbar-js-version","JS",globalThis.Domma?.version||s.js]]){const n=document.getElementById(e);n&&(n.textContent=o||"",n.parentElement.hidden=!o,n.hidden=!o)}}export function sidebarPreferenceKey(r){return"sidebar.visible."+(r?.id||"anonymous")}export function wireAccountMenu(r){c("topbar-account"),c("topbar-versions-menu");const e=document.getElementById("topbar-show-sidebar");if(!e||e.dataset.wired)return;e.dataset.wired="1";const i=sidebarPreferenceKey(r);let o=!0;try{o=S.get(i)!==!1}catch{}const n=a=>{!a&&document.body.classList.contains("dm-sidebar-open")&&document.getElementById("topbar-sidebar-toggle")?.click(),document.body.classList.toggle("dm-sidebar-hidden",!a),e.checked=a};n(o),e.addEventListener("change",()=>{n(e.checked);try{S.set(i,e.checked)}catch{}})}const d=new Map;function c(r){const e=document.getElementById(r);if(!e||e.dataset.wired)return;e.dataset.wired="1";const i=e.querySelector("button"),o=e.querySelector(".topbar-account-menu"),n=t=>{t&&document.dispatchEvent(new CustomEvent("dm:topbar-menu-open",{detail:{owner:e}})),o.hidden=!t,i.setAttribute("aria-expanded",String(t))};i.addEventListener("click",()=>n(o.hidden)),i.addEventListener("keydown",t=>{t.key==="ArrowDown"&&(t.preventDefault(),n(!0),o.querySelector("a, input")?.focus())}),e.addEventListener("click",t=>{t.target.closest("a")&&n(!1)}),d.get(r)?.abort();const a=new AbortController;d.set(r,a),document.addEventListener("click",t=>{e.contains(t.target)||n(!1)},{signal:a.signal}),document.addEventListener("dm:topbar-menu-open",t=>{t.detail.owner!==e&&n(!1)},{signal:a.signal}),document.addEventListener("keydown",t=>{!t.defaultPrevented&&t.key==="Escape"&&!o.hidden&&(n(!1),i.focus(),t.preventDefault())},{signal:a.signal}),window.addEventListener("hashchange",()=>n(!1),{signal:a.signal})}
|
|
1
|
+
let s={};export function renderFrameworkVersions(r){r&&(s=r);for(const[e,i,o]of[["topbar-reactive-version","Reactive",s.reactive],["topbar-js-version","JS",globalThis.Domma?.version||s.js]]){const n=document.getElementById(e);n&&(n.textContent=o||"",n.parentElement.hidden=!o,n.hidden=!o)}}export function sidebarPreferenceKey(r){return"sidebar.visible."+(r?.id||"anonymous")}export function wireAccountMenu(r){c("topbar-account"),c("topbar-versions-menu");const e=document.getElementById("topbar-show-sidebar");if(!e||e.dataset.wired)return;e.dataset.wired="1";const i=sidebarPreferenceKey(r);let o=!0;try{o=S.get(i)!==!1}catch{}const n=a=>{!a&&document.body.classList.contains("dm-sidebar-open")&&document.getElementById("topbar-sidebar-toggle")?.click(),document.body.classList.toggle("dm-sidebar-hidden",!a),e.checked=a};n(o),e.addEventListener("change",()=>{n(e.checked);try{S.set(i,e.checked)}catch{}})}const d=new Map;function c(r){const e=document.getElementById(r);if(!e||e.dataset.wired)return;e.dataset.wired="1";const i=e.querySelector("button"),o=e.querySelector(".topbar-account-menu"),n=t=>{t&&document.dispatchEvent(new CustomEvent("dm:topbar-menu-open",{detail:{owner:e}})),o.hidden=!t,i.setAttribute("aria-expanded",String(t))};i.addEventListener("click",()=>n(o.hidden)),i.addEventListener("keydown",t=>{t.key==="ArrowDown"&&(t.preventDefault(),n(!0),o.querySelector("a, input, button:not(:disabled)")?.focus())}),e.addEventListener("click",t=>{t.target.closest("a")&&n(!1)}),d.get(r)?.abort();const a=new AbortController;d.set(r,a),document.addEventListener("click",t=>{e.contains(t.target)||n(!1)},{signal:a.signal}),document.addEventListener("dm:topbar-menu-open",t=>{t.detail.owner!==e&&n(!1)},{signal:a.signal}),document.addEventListener("keydown",t=>{!t.defaultPrevented&&t.key==="Escape"&&!o.hidden&&(n(!1),i.focus(),t.preventDefault())},{signal:a.signal}),window.addEventListener("hashchange",()=>n(!1),{signal:a.signal})}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import{apiRequest as g}from"../api.js";const C=e=>e>0&&e<.001?"<0.1%":`${Math.round(Math.min(1,Math.max(0,e))*1e3)/10}%`;export function cacheSummary(e={}){if(!e.enabled)return{usage:"Off",detail:"Response caching is off.",hitRate:""};const n=Number.isFinite(e.size)&&e.size>=0?e.size:null,c=Number.isFinite(e.maxItems)&&e.maxItems>0?e.maxItems:null;return{usage:n!==null&&c?C(n/c):"On",detail:n===null?"Capacity unavailable.":c?`${n.toLocaleString()} / ${c.toLocaleString()} entries`:`${n.toLocaleString()} entries`,hitRate:Number.isFinite(e.hitRate)?C(e.hitRate):"No requests yet"}}let b;export function wireNotificationCacheMenu(){b?.abort(),b=new AbortController;const{signal:e}=b,n=document.getElementById("topbar-notifications"),c=document.getElementById("topbar-notifications-menu");if(!n||!c)return;c.querySelector("[data-topbar-cache]")?.remove();const a=document.createElement("section");a.className="topbar-cache",a.dataset.topbarCache="",a.hidden=!0,a.setAttribute("aria-label","Response cache"),a.innerHTML=`<div class="topbar-cache-row"><span>Cache usage</span><strong class="topbar-version-badge" data-cache-usage></strong></div>
|
|
2
|
+
<p class="topbar-cache-detail" data-cache-detail></p>
|
|
3
|
+
<div class="topbar-cache-row" data-cache-hits-row><span>Hit rate</span><span data-cache-hits></span></div>
|
|
4
|
+
<div class="topbar-cache-actions"><button type="button" class="btn btn-sm btn-ghost" data-cache-refresh>Refresh</button>
|
|
5
|
+
<button type="button" class="btn btn-sm btn-outline" data-cache-clear disabled>Clear cache</button></div>
|
|
6
|
+
<p class="topbar-cache-detail" role="status" aria-live="polite" data-cache-message></p>`,c.appendChild(a);const i=t=>a.querySelector(t),r=i("[data-cache-clear]"),o=i("[data-cache-refresh]"),l=i("[data-cache-message]");let h=null,u=!1,d=!1,s=null;const m=t=>{s=cacheSummary(t),i("[data-cache-usage]").textContent=s.usage,i("[data-cache-detail]").textContent=s.detail,i("[data-cache-hits]").textContent=s.hitRate,i("[data-cache-hits-row]").hidden=!s.hitRate,a.hidden=!1,r.disabled=d},f=t=>{[401,403].includes(t.status)?(h=!1,a.hidden=!0):(a.hidden=!1,l.textContent=t.message||"Cache unavailable. Try Refresh.")},p=async()=>{if(!(e.aborted||h===!1||u||d)){u=!0,o.disabled=!0,r.disabled=!0,l.textContent="Refreshing cache...";try{const t=await g("/cache/status");if(e.aborted)return;h=!0,m(t),l.textContent=""}catch(t){e.aborted||f(t)}finally{u=!1,e.aborted||(o.disabled=!1,r.disabled=!s)}}};document.addEventListener("dm:topbar-menu-open",t=>{t.detail?.owner===n&&p()},{signal:e}),o.addEventListener("click",p,{signal:e}),r.addEventListener("click",async()=>{if(!(d||u||h!==!0||!s)){d=!0,r.disabled=!0,o.disabled=!0;try{if(!await E.confirm("Clear the response cache? Public pages will be rendered again as visitors open them.")||e.aborted)return;l.textContent="Clearing cache...";const t=await g("/cache/clear",{method:"POST"});if(e.aborted)return;m(t),l.textContent="Cache cleared.",E.toast("Cache cleared.",{type:"success"}),window.dispatchEvent(new CustomEvent("dm:cache-cleared"))}catch(t){e.aborted||f(t)}finally{d=!1,e.aborted||(o.disabled=!1,r.disabled=!s)}}},{signal:e})}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import{api as
|
|
2
|
-
${
|
|
1
|
+
import{api as A,apiRequest as k}from"../api.js";import{sidebarCount as S}from"./tool-kit.js";import{severityOf as C}from"./notifications-arrange.js";import{wireNotificationCacheMenu as L}from"./topbar-cache.js";const q=[["critical","Critical","alert-circle","danger"],["warning","Warnings","alert-triangle","warn"],["info","Information","info","info"],["success","Success","check-circle","ok"]];export function notificationGroups(p=[]){return q.map(([i,c,u,e])=>{const d=p.filter(l=>C(l)===i);return{type:i,label:c,icon:u,tone:e,total:d.length,unread:d.filter(l=>l.unread).length}})}let y;export function wireNotificationMenu({refreshBadge:p}){y?.abort(),y=new AbortController;const{signal:i}=y,c=document.getElementById("topbar-notifications");if(!c)return;const u=c.querySelector("#topbar-bell"),e=c.querySelector("#topbar-notifications-menu"),d=document.createElement("div");d.dataset.notificationItems="",e.replaceChildren(d),L();let l=0,m=!1;const f=()=>{l++,e.hidden=!0,u.setAttribute("aria-expanded","false")},h=t=>{const n=document.createElement("p");n.className="nt-menu-status",n.setAttribute("role","status"),n.textContent=t,d.replaceChildren(n)},g=t=>{const n=d.contains(document.activeElement)?document.activeElement:null,o=n?.dataset.dismiss,s=n?.getAttribute("href"),r=n?.hasAttribute("data-read-all"),v=notificationGroups(t);d.innerHTML=`<div class="nt-menu-heading" aria-live="polite">Notifications <span>${v.reduce((a,$)=>a+$.unread,0)} unread</span></div>
|
|
2
|
+
${v.map(a=>`<div class="nt-menu-row">
|
|
3
3
|
<a class="nt-menu-type" href="#/system/notifications?type=${a.type}">
|
|
4
4
|
<span class="nt-menu-icon is-${a.tone}"><span data-icon="${a.icon}"></span></span>
|
|
5
5
|
<span>${a.label}<small>${a.unread} unread / ${a.total} total</small></span>
|
|
6
6
|
</a>
|
|
7
|
-
<button type="button" class="nt-menu-dismiss" data-dismiss="${a.type}" data-count="${a.total}" aria-label="Dismiss ${a.total} ${a.label.toLowerCase()} notifications" ${!a.total||
|
|
7
|
+
<button type="button" class="nt-menu-dismiss" data-dismiss="${a.type}" data-count="${a.total}" aria-label="Dismiss ${a.total} ${a.label.toLowerCase()} notifications" ${!a.total||m?"disabled":""}>Dismiss</button>
|
|
8
8
|
</div>`).join("")}
|
|
9
9
|
${t.length?"":`<p class="nt-menu-status">No notifications - you're all caught up.</p>`}
|
|
10
10
|
<div class="nt-menu-footer"><a href="#/system/notifications?type=all">View all notifications</a>
|
|
11
|
-
<button type="button" data-read-all ${
|
|
11
|
+
<button type="button" data-read-all ${m||!t.some(a=>a.unread)?"disabled":""}>Mark all as read</button></div>`,Domma.icons.scan(e),o?(e.querySelector(`[data-dismiss="${o}"]:not(:disabled)`)||e.querySelector(`a[href$="type=${o}"]`))?.focus():s?[...e.querySelectorAll("a")].find(a=>a.getAttribute("href")===s)?.focus():r&&(e.querySelector("[data-read-all]:not(:disabled)")||e.querySelector(".nt-menu-footer a"))?.focus()},b=async()=>{const t=++l;try{const n=await A.system.notifications.list();if(i.aborted||t!==l||e.hidden)return;g(Array.isArray(n)?n:[])}catch{!i.aborted&&t===l&&!e.hidden&&h("Could not load notifications. Close and reopen to try again.")}},w=async t=>{document.dispatchEvent(new CustomEvent("dm:topbar-menu-open",{detail:{owner:c}})),e.hidden=!1,u.setAttribute("aria-expanded","true"),h("Loading notifications..."),await b(),t&&!e.hidden&&e.querySelector("a, button:not(:disabled)")?.focus()};u.addEventListener("click",()=>e.hidden?w(!1):f(),{signal:i}),u.addEventListener("keydown",t=>{t.key==="ArrowDown"&&(t.preventDefault(),w(!0))},{signal:i}),e.addEventListener("click",async t=>{const n=t.target.closest("a");if(n){const r=new URLSearchParams(n.hash.split("?")[1]||"").get("type");document.dispatchEvent(new CustomEvent("dm:notifications-filter",{detail:{type:r}})),f();return}const o=t.target.closest("button");if(!o||o.disabled||m||!o.hasAttribute("data-dismiss")&&!o.hasAttribute("data-read-all"))return;const s=o.dataset.dismiss;m=!0;try{if(s==="critical"&&!await E.confirm(`Dismiss all ${o.dataset.count} critical notifications? They are hidden for you only.`))return;d.querySelectorAll("button").forEach(r=>{r.disabled=!0}),await k(`/system/notifications/${s?"dismiss-all":"read-all"}`,{method:"POST",...s?{body:JSON.stringify({severity:s})}:{}}),document.dispatchEvent(new CustomEvent("dm:notifications-changed")),S("#/system/notifications"),await p()}catch(r){E.toast(`Could not update notifications: ${r.message||r}`,{type:"error"})}finally{m=!1,!i.aborted&&!e.hidden&&await b()}},{signal:i}),e.addEventListener("keydown",t=>{const n=[...e.querySelectorAll("a, button:not(:disabled)")].filter(r=>!r.closest("[hidden]")),o=n.indexOf(document.activeElement);if(o<0)return;let s;t.key==="ArrowDown"&&(s=(o+1)%n.length),t.key==="ArrowUp"&&(s=(o-1+n.length)%n.length),t.key==="Home"&&(s=0),t.key==="End"&&(s=n.length-1),s!==void 0&&(t.preventDefault(),n[s].focus())},{signal:i}),document.addEventListener("click",t=>{c.contains(t.target)||f()},{signal:i}),document.addEventListener("focusin",t=>{c.contains(t.target)||f()},{signal:i}),document.addEventListener("dm:topbar-menu-open",t=>{t.detail.owner!==c&&f()},{signal:i}),document.addEventListener("keydown",t=>{!t.defaultPrevented&&t.key==="Escape"&&!e.hidden&&(f(),u.focus(),t.preventDefault())},{signal:i}),window.addEventListener("hashchange",f,{signal:i})}
|
|
@@ -11,6 +11,13 @@
|
|
|
11
11
|
<code>config/site.json</code>. Themes and fonts have their own screen, <strong>Content >
|
|
12
12
|
Theme</strong>.</p>
|
|
13
13
|
|
|
14
|
+
<p>Search defaults, the default share image and Google Business Profile are in the
|
|
15
|
+
<strong>SEO</strong> tool's settings. The Google Analytics measurement ID is in
|
|
16
|
+
<strong>Analytics</strong> settings. Cookie consent stays here.</p>
|
|
17
|
+
|
|
18
|
+
<p>Public site search is configured in the <strong>Search</strong> tab, including its trigger,
|
|
19
|
+
position and query preview. Existing Search links open that tab.</p>
|
|
20
|
+
|
|
14
21
|
<table class="table table-sm">
|
|
15
22
|
<thead>
|
|
16
23
|
<tr>
|
|
@@ -35,11 +42,6 @@
|
|
|
35
42
|
browser tab, for everyone who signs in; empty, it uses the site title. The admin logo
|
|
36
43
|
takes priority over the admin icon.</td>
|
|
37
44
|
</tr>
|
|
38
|
-
<tr>
|
|
39
|
-
<td>SEO</td>
|
|
40
|
-
<td>Default page title, title separator and default description. The full SEO Tool is
|
|
41
|
-
<strong>SEO</strong> in the sidebar.</td>
|
|
42
|
-
</tr>
|
|
43
45
|
<tr>
|
|
44
46
|
<td>Footer</td>
|
|
45
47
|
<td>Copyright text, layout, blurb, footer blocks and social links.
|
|
@@ -18,15 +18,14 @@
|
|
|
18
18
|
<button type="button" class="tab-item active">General <i class="stx-dot" data-bind-hidden="!tabs.value.general" title="Changed"></i></button>
|
|
19
19
|
<button type="button" class="tab-item">Branding <i class="stx-dot" data-bind-hidden="!tabs.value.branding" title="Changed"></i></button>
|
|
20
20
|
<button type="button" class="tab-item">Favicon <i class="stx-dot" data-bind-hidden="!tabs.value.favicon" title="Changed"></i></button>
|
|
21
|
-
<button type="button" class="tab-item">SEO <i class="stx-dot" data-bind-hidden="!tabs.value.seo" title="Changed"></i></button>
|
|
22
21
|
<button type="button" class="tab-item">Footer <i class="stx-dot" data-bind-hidden="!tabs.value.footer" title="Changed"></i></button>
|
|
23
22
|
<button type="button" class="tab-item">Email <i class="stx-dot" data-bind-hidden="!tabs.value.email" title="Changed"></i><i class="stx-dot is-bad" data-bind-hidden="!noSmtp.value || tabs.value.email" title="No SMTP host"></i></button>
|
|
24
23
|
<button type="button" class="tab-item">Page navigation <i class="stx-dot" data-bind-hidden="!tabs.value.pageNavigation" title="Changed"></i></button>
|
|
25
24
|
<button type="button" class="tab-item">Cookie consent <i class="stx-dot" data-bind-hidden="!tabs.value.cookies" title="Changed"></i></button>
|
|
26
|
-
<button type="button" class="tab-item">Analytics <i class="stx-dot" data-bind-hidden="!tabs.value.analytics" title="Changed"></i></button>
|
|
27
25
|
<button type="button" class="tab-item">Breadcrumbs <i class="stx-dot" data-bind-hidden="!tabs.value.breadcrumbs" title="Changed"></i></button>
|
|
28
26
|
<button type="button" class="tab-item">Reduce motion <i class="stx-dot" data-bind-hidden="!tabs.value.motion" title="Changed"></i></button>
|
|
29
27
|
<button type="button" class="tab-item">Cache</button>
|
|
28
|
+
<button type="button" class="tab-item" data-site-search-tab>Search <i class="stx-dot" data-site-search-dirty hidden title="Changed"></i></button>
|
|
30
29
|
</div>
|
|
31
30
|
|
|
32
31
|
<!-- General -->
|
|
@@ -92,6 +91,9 @@
|
|
|
92
91
|
<div class="tab-panel">
|
|
93
92
|
<div class="stx-card-columns">
|
|
94
93
|
<div class="stx-card"><div class="stx-card-title"><span data-icon="globe" data-icon-size="16" aria-hidden="true"></span>Public site</div><div class="stx-grid">
|
|
94
|
+
<div class="stx-setting stx-wide"><label class="form-label" for="stx-brand-enabled">Show public site branding
|
|
95
|
+
<span data-help="Shows the name and mark in the navbar and footer. Off hides them and keeps the saved values for next time."></span></label>
|
|
96
|
+
<input id="stx-brand-enabled" type="checkbox" class="form-switch" data-model="brandEnabled.value"></div>
|
|
95
97
|
<div><label class="form-label" for="stx-brandText">Brand name</label>
|
|
96
98
|
<input id="stx-brandText" type="text" class="form-input" placeholder="My Site" data-model="brandText.value">
|
|
97
99
|
</div>
|
|
@@ -111,6 +113,9 @@
|
|
|
111
113
|
</div><p class="text-muted">The logo takes priority over the icon. Set the browser icon in the Favicon tab.</p></div>
|
|
112
114
|
<div class="stx-card"><div class="stx-card-title"><span data-icon="layout" data-icon-size="16" aria-hidden="true"></span>Admin
|
|
113
115
|
<span data-help-title="Admin name and mark" data-help="The name and logo or icon at the top of the admin - in the top bar, the sidebar and the browser tab - for everyone who signs in. Leave the name empty to use the site title."></span></div><div class="stx-grid">
|
|
116
|
+
<div class="stx-setting stx-wide"><label class="form-label" for="stx-admin-brand-enabled">Show admin branding
|
|
117
|
+
<span data-help="Shows the name and mark in the admin top bar and sidebar, and the name in browser tab titles. Off hides them and keeps the saved values."></span></label>
|
|
118
|
+
<input id="stx-admin-brand-enabled" type="checkbox" class="form-switch" data-model="adminBrandEnabled.value"></div>
|
|
114
119
|
<div><label class="form-label" for="stx-adminBrandTitle">Admin name</label>
|
|
115
120
|
<input id="stx-adminBrandTitle" type="text" class="form-input" placeholder="Uses the site title when empty" data-model="adminBrandTitle.value">
|
|
116
121
|
</div>
|
|
@@ -151,30 +156,6 @@
|
|
|
151
156
|
</div>
|
|
152
157
|
</div>
|
|
153
158
|
|
|
154
|
-
<!-- SEO -->
|
|
155
|
-
<div class="tab-panel">
|
|
156
|
-
<div class="stx-card">
|
|
157
|
-
<div class="stx-card-title"><span data-icon="search" data-icon-size="16" aria-hidden="true"></span>Search engines
|
|
158
|
-
<span data-help-title="SEO defaults" data-help="Used on every page that does not set its own. A page's title becomes “Page title” + separator + default site title."></span></div>
|
|
159
|
-
<div class="stx-grid">
|
|
160
|
-
<div>
|
|
161
|
-
<label class="form-label" for="stx-seo-title">Default site title</label>
|
|
162
|
-
<input id="stx-seo-title" type="text" class="form-input" placeholder="My Site" data-model="seoTitle.value">
|
|
163
|
-
</div>
|
|
164
|
-
<div>
|
|
165
|
-
<label class="form-label" for="stx-seo-sep">Title separator</label>
|
|
166
|
-
<input id="stx-seo-sep" type="text" class="form-input" placeholder=" | " data-model="seoSeparator.value">
|
|
167
|
-
</div>
|
|
168
|
-
<div class="stx-wide">
|
|
169
|
-
<label class="form-label" for="stx-seo-desc">Default meta description
|
|
170
|
-
<span data-help="The summary search engines show under a page that has no description of its own. About 150 characters is what they display."></span></label>
|
|
171
|
-
<input id="stx-seo-desc" type="text" class="form-input" placeholder="Brief description of your site" data-model="seoDescription.value">
|
|
172
|
-
<small class="stx-hint" data-bind-text="seoLength.value"></small>
|
|
173
|
-
</div>
|
|
174
|
-
</div>
|
|
175
|
-
</div>
|
|
176
|
-
</div>
|
|
177
|
-
|
|
178
159
|
<!-- Footer -->
|
|
179
160
|
<div class="tab-panel">
|
|
180
161
|
<div class="stx-card-columns">
|
|
@@ -513,21 +494,6 @@
|
|
|
513
494
|
</div>
|
|
514
495
|
</div>
|
|
515
496
|
|
|
516
|
-
<!-- Analytics -->
|
|
517
|
-
<div class="tab-panel">
|
|
518
|
-
<div class="stx-card">
|
|
519
|
-
<div class="stx-card-title"><span data-icon="bar-chart" data-icon-size="16" aria-hidden="true"></span>Google Analytics
|
|
520
|
-
<span data-help-title="Google Analytics" data-help="Paste the measurement ID of your Google Analytics 4 web stream (Admin > Data streams). Google Analytics sets cookies, so it loads only for visitors who accept Analytics cookies in the cookie banner - the banner is shown whenever an ID is set. Leave it empty to switch Google Analytics off. The built-in Analytics Tool is separate and needs no cookies."></span></div>
|
|
521
|
-
<div class="stx-grid">
|
|
522
|
-
<div class="stx-wide"><label class="form-label" for="stx-ga-id">Measurement ID</label>
|
|
523
|
-
<input id="stx-ga-id" type="text" class="form-input" placeholder="G-XXXXXXXXXX" autocomplete="off" spellcheck="false" data-model="gaId.value">
|
|
524
|
-
<small class="text-danger" data-bind-hidden="!gaIdError.value" data-bind-text="gaIdError.value"></small></div>
|
|
525
|
-
</div>
|
|
526
|
-
<p class="stx-note">Loads only after a visitor accepts Analytics cookies. Visitors who reject them, or have not answered yet, are not sent to Google.</p>
|
|
527
|
-
<p class="stx-note" data-bind-hidden="!gaForcesBanner.value">The cookie banner is switched off under Cookie consent, but it will still be shown while Google Analytics is set - it is how visitors give consent.</p>
|
|
528
|
-
</div>
|
|
529
|
-
</div>
|
|
530
|
-
|
|
531
497
|
<!-- Breadcrumbs -->
|
|
532
498
|
<div class="tab-panel">
|
|
533
499
|
<div class="stx-card-columns">
|
|
@@ -678,6 +644,7 @@
|
|
|
678
644
|
<div class="stx-list" data-each="cacheRows.value key=id"></div>
|
|
679
645
|
</div>
|
|
680
646
|
</div>
|
|
647
|
+
<div class="tab-panel" data-site-search-panel><p role="status">Open Search to load its settings.</p></div>
|
|
681
648
|
</div>
|
|
682
649
|
</div>
|
|
683
650
|
|
|
@@ -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()])}};
|