domma-cms 1.3.0 → 1.5.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/admin/css/admin.css +1 -1
- package/admin/js/app.js +2 -2
- package/admin/js/lib/dashboards-arrange.js +3 -0
- package/admin/js/lib/dashboards-strip.js +1 -0
- package/admin/js/lib/plugins-arrange.js +1 -1
- package/admin/js/lib/sidebar-frequent.js +1 -1
- package/admin/js/lib/sidebar-renderer.js +7 -4
- package/admin/js/templates/dashboards.html +2 -0
- package/admin/js/templates/docs/usage-dashboard.html +16 -12
- package/admin/js/templates/docs/usage-plugins.html +9 -0
- package/admin/js/templates/docs/usage-tools.html +10 -0
- package/admin/js/views/dashboard-board.js +18 -19
- package/admin/js/views/dashboard.js +2 -2
- package/admin/js/views/dashboards.js +1 -0
- package/admin/js/views/index.js +1 -1
- package/admin/js/views/plugins.js +9 -9
- package/package.json +1 -1
- package/plugins/_lib/admin/mail/compose-window.js +23 -2
- package/plugins/_lib/admin/mail/keyboard.js +14 -0
- package/plugins/_lib/admin/mail/scheduling.js +22 -0
- package/server/routes/api/auth.js +2 -2
- package/server/routes/api/plugin-marketplace.js +9 -3
- package/server/routes/api/sidebar.js +17 -6
- package/server/services/dashboardSettings.js +8 -4
- package/server/services/managerClient.js +28 -0
- package/server/services/widgets.js +5 -11
- package/admin/js/templates/my-dashboard.html +0 -3
- package/admin/js/views/my-dashboard.js +0 -1
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
import{api as
|
|
2
|
-
<summary${y}>${s} <span class="sidebar-text">${
|
|
3
|
-
<div class="sidebar-children"
|
|
4
|
-
</details>`}return`<a href="${a}" class="sidebar-link" data-url="${a}"${y}${c}>${s} <span class="sidebar-text">${d}</span>${Ee(e,n)}${v}</a>`}async function Te(e=[],n=[],o=null){const t=f=>f.then(x=>Array.isArray(x)?x.length:Array.isArray(x?.entries)?x.entries.length:0).catch(()=>0);k=Be(e,o);const s=new Set(k.map(f=>f.url)),d={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"},a=(f,x)=>_(n,f)&&!s.has(d[f])?x():Promise.resolve(0),[i,u,l,c,m,y,v,b,C,A,L,T,R,I]=await Promise.all([a("pages",()=>t(h.pages.list())),a("media",()=>t(h.media.list())),a("menus",()=>t(h.menus.list())),a("context-menus",()=>t(h.contextMenus.list())),a("theme",()=>h.theme.get().then(f=>(f?.overrides||[]).length).catch(()=>0)),a("collections",()=>t(h.collections.list())),a("forms",()=>t(h.forms.list())),a("views",()=>t(h.views.list())),a("actions",()=>t(h.actions.list())),a("blocks",()=>t(h.blocks.list())),a("components",()=>t(h.components.list())),a("users",()=>t(h.users.list())),a("plugins",()=>t(h.plugins.list())),a("notifications",()=>h.system?.notifications?.unreadCount?.().then(f=>f?.count??0).catch(()=>0)??Promise.resolve(0))]),j={};await Promise.all(k.map(async f=>{const x=await te(f);x&&(j[f.url]=x)}));const B={"#/pages":i,"#/media":u,"#/menus":l,"#/context-menus":c,"#/theme":m,"#/collections":y,"#/forms":v,"#/views":b,"#/actions":C,"#/blocks":A,"#/components":L,"#/users":T,"#/plugins":R,"#/system/notifications":I},P={};for(const[f,x]of Object.entries(B)){if(s.has(f))continue;const[r,w]=Me[f]??["item","items"],p=z(x);p&&(P[f]={...p,label:`${p.count} ${p.count===1?r:w}`})}return{...P,...j}}let k=[];const Ne="dm:sidebar-count",je=6e4;function Be(e=[],n=null){const o=[],t=s=>{if(s&&!(n&&typeof s.url=="string"&&s.url.startsWith("#/")&&!ie(n,s.url.slice(1)))){s.url&&typeof s.countUrl=="string"&&s.countUrl&&o.push({url:s.url,countUrl:s.countUrl,text:s.text||"",countLabel:s.countLabel||"",inventory:s.inventory===!0});for(const d of[...s.children||[],...s.items||[]])t(d)}};for(const s of e??[])t(s?.item??s);return o}async function te(e){try{const n=new Date,o=`${n.getFullYear()}-${String(n.getMonth()+1).padStart(2,"0")}-${String(n.getDate()).padStart(2,"0")}`,t=e.countUrl+(e.countUrl.includes("?")?"&":"?")+`today=${o}`;return z(await H.get(t),{fallbackLabel:e.countLabel||e.text})}catch{return null}}function ne(e,n,o){const t=[...e.querySelectorAll("a.sidebar-link")].filter(s=>s.dataset.url===n);if(t.length){for(const s of t){const d=s.querySelector(".sidebar-badge[data-count-for]");if(!o||!(o.count>0)&&!o.showZero)d?.remove();else{const a=document.createRange();a.selectNodeContents(s);const i=a.createContextualFragment(Y(n,o)).firstElementChild;d?d.replaceWith(i):s.querySelector(".sidebar-text")?.after(i)}}W.set(n,o),J(e)}}const W=new Map;async function D(e,n=null){const o=n?k.filter(t=>t.url===n):k;await Promise.all(o.map(async t=>{const s=await te(t),d=W.get(t.url);JSON.stringify(d??null)!==JSON.stringify(s)&&ne(e,t.url,s)}))}function Pe(e){e.dataset.liveCounts||(e.dataset.liveCounts="1",window.addEventListener(Ne,n=>{const o=n.detail||{};if(o.url)if(o.count!==void 0){const t=k.find(s=>s.url===o.url);ne(e,o.url,z(o,{fallbackLabel:t?.countLabel||t?.text||""}))}else D(e,o.url)}),setInterval(()=>{!document.hidden&&document.body.contains(e)&&D(e)},je),document.addEventListener("visibilitychange",()=>{document.hidden||D(e)}),_e(e))}function se(e,n,o){const t=(a,i,u)=>{const l=document.createElement(a);return i&&(l.className=i),u!=null&&(l.textContent=String(u)),l};if(!o){e.textContent=n;return}e.appendChild(t("strong","dm-badge-pop-title",o.title||n)),(o.rows?.length||!o.items?.length)&&e.appendChild(t("p","dm-badge-pop-sub",n));const s=Array.isArray(o.rows)?o.rows.slice(0,6):[];if(s.length){const a=t("dl","dm-badge-pop-rows");for(const i of s)a.appendChild(t("dt",null,i.label)),a.appendChild(t("dd",i.tone==="danger"?"is-danger":null,i.value));e.appendChild(a)}const d=Array.isArray(o.items)?o.items.slice(0,6):[];if(d.length){const a=t("ul","dm-badge-pop-items");for(const i of d){const u=t("li",i.tone==="danger"?"is-danger":null);u.appendChild(t("span","dm-badge-pop-text",i.text)),i.meta&&u.appendChild(t("span","dm-badge-pop-meta",i.meta)),a.appendChild(u)}e.appendChild(a)}else o.empty&&e.appendChild(t("p","dm-badge-pop-empty",o.empty))}function _e(e){let n=null,o=null;const t=()=>e.classList.contains("is-collapsed"),s=()=>{clearTimeout(o),n?.remove(),n=null},d=(l,c)=>{s(),o=setTimeout(()=>{n=document.createElement("div"),n.className="dm-help-pop dm-badge-pop",n.setAttribute("role","tooltip"),c(n),document.body.appendChild(n);const m=l.getBoundingClientRect(),y=n.getBoundingClientRect(),v=Math.min(m.right+10,innerWidth-y.width-8);n.style.left=`${Math.max(8,v)}px`,n.style.top=`${Math.max(8,Math.min(m.top+m.height/2-y.height/2,innerHeight-y.height-8))}px`},150)},a=l=>{const c=l?.dataset.badgeLabel;if(!c)return s();d(l,m=>se(m,c,F.get(l.dataset.countFor)))},i=l=>{const c=l.querySelector(":scope > .sidebar-text")?.textContent.trim()||"",m=l.querySelector(":scope > .sidebar-badge[data-badge-label]:not([hidden])");d(l,y=>{const v=m&&F.get(m.dataset.countFor);if(v)return se(y,m.dataset.badgeLabel,{...v,title:v.title||c});if(!m){y.textContent=c;return}const b=document.createElement("strong");b.className="dm-badge-pop-title",b.textContent=c;const C=document.createElement("p");C.className="dm-badge-pop-sub",C.textContent=m.dataset.badgeLabel,y.append(b,C)})},u=l=>{const c=l.closest?.("a.sidebar-link, summary");return c&&e.contains(c)?c:null};e.addEventListener("mouseover",l=>{if(t()){const m=u(l.target);m&&!m.contains(l.relatedTarget)&&i(m);return}const c=l.target.closest?.(".sidebar-badge[data-badge-label]");c&&a(c)}),e.addEventListener("mouseout",l=>{if(t()){const c=u(l.target);c&&!c.contains(l.relatedTarget)&&s();return}l.target.closest?.(".sidebar-badge[data-badge-label]")&&s()}),e.addEventListener("focusin",l=>{const c=l.target.closest?.("a, summary");if(t()&&c&&e.contains(c)&&c.querySelector(":scope > .sidebar-text"))return i(c);const m=c?.querySelector(":scope > .sidebar-badge[data-badge-label]:not([hidden])");m?a(m):s()}),e.addEventListener("focusout",s),e.addEventListener("click",s),window.addEventListener("scroll",s,!0),window.addEventListener("hashchange",s)}async function He(e=[]){if(!_(e,"settings"))return"Admin";try{const n=await h.settings.get();return n?.adminBrand?.title||n?.title||"Admin"}catch{return"Admin"}}export async function renderAdminSidebar({mount:e,permissions:n,adminScope:o=null}){const t=$(e).get(0);if(!t)return;let s,d=null,a=null,i=null,u=null,l=null;try{const r=await h.get("/sidebar/menu");s=Array.isArray(r?.items)?r.items:null,d=r?.variant||null,a=r?.position||null,i=r?.style||null,u=r?.float||null,l=r?.highlight||null}catch{s=null}(!s||!s.some(r=>r&&(!r.hidden||r.restricted)))&&(console.warn("[admin-sidebar] No admin-sidebar menu found; using fallback tree"),s=Le.slice());let c=[];try{c=await H.get("/api/sidebar/registered-items")||[]}catch{}s=me(s,V),s=ce(s,c.filter(r=>(r?.item??r)?.url!==V)),s=de(s),s=fe(s,[ue()]);const m=await qe(n);m.length&&Fe(s,m),s=he(s,n,o),s=pe(s);const y=Ce(s,U(O()));y&&(s=[y,...s]);const[v,b]=await Promise.all([Te(c.filter(r=>_(n,r?.item?.permission)),n,o).catch(()=>({})),He(n)]),C=d?` dm-admin-sidebar--${g(d)}`:"";function A(r,w="px"){if(r==null)return r;const p=String(r).trim();return/^\d+(\.\d+)?$/.test(p)?`${p}${w}`:p}let L="";if(i){const r=[];i.fontFamily&&r.push(`font-family: ${i.fontFamily}, sans-serif`),i.fontSize&&r.push(`font-size: ${A(i.fontSize)}`),i.fontWeight&&r.push(`font-weight: ${i.fontWeight}`),i.letterSpacing&&r.push(`letter-spacing: ${A(i.letterSpacing,"em")}`);const w=[];if(r.length&&w.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 { ${r.join("; ")} }`),i.iconSize){const p=A(i.iconSize);w.push(`#admin-sidebar .dm-admin-sidebar [data-icon], #admin-sidebar .dm-admin-sidebar [data-icon] svg { width: ${p} !important; height: ${p} !important; }`)}w.length&&(L=`<style data-admin-sidebar-style>${w.join(" ")}</style>`)}const T=ye(n)?be():"",R=`<div class="dm-admin-sidebar-header"><span data-icon="layout"></span><span class="dm-admin-sidebar-title">${g(b)}</span>${T}${xe()}</div>`,I=s.some(r=>r&&r.type==="spacer"&&r.size==="flex")?" dm-admin-sidebar--has-flex-spacer":"",j=`${L}<nav class="dm-admin-sidebar${C}${I}">${R}${s.map(r=>ee(r,v)).join("")}</nav>`,B=document.createRange();B.selectNodeContents(t);const P=B.createContextualFragment(j);t.replaceChildren(P),Domma.icons.scan(t),t.querySelector(".dm-admin-sidebar-arrange")?.addEventListener("click",()=>ve(t));for(const r of k)W.set(r.url,v[r.url]??null);J(t),Pe(t),t.removeAttribute("data-menu-hover"),t.removeAttribute("data-menu-active"),t.style.removeProperty("--dm-menu-highlight"),re(t,{highlight:l});const f=document.querySelector(".dashboard-wrapper");["position","top","bottom","left","right","zIndex","maxHeight","transform"].forEach(r=>{t.style[r]=""}),t.classList.remove("dm-menu-floating"),f&&(f.style.paddingLeft="",f.style.paddingRight=""),t.__dmGutter=()=>{if(a!=="floating"||!f)return;const r=u&&u.anchor||"TL",w=Number.isFinite(Number(u?.offsetX))?Number(u.offsetX):16,p=t.classList.contains("is-collapsed")?we:parseInt(t.style.width,10)||t.getBoundingClientRect().width||260,q=Math.round(p+w*2)+"px";r==="R"||r==="TR"||r==="BR"?f.style.paddingRight=q:f.style.paddingLeft=q},a==="floating"&&ae(t,{position:"floating",float:u||{}}),t.querySelectorAll("details").forEach(r=>{r.addEventListener("toggle",function(){const w=this.getAttribute("data-state-key");w&&S.set(w,this.open);const p=this.querySelector(":scope > .sidebar-children");if(p)if(this.open){const q=p.scrollHeight;p.style.maxHeight="0",requestAnimationFrame(()=>{p.style.maxHeight=q+"px",p.addEventListener("transitionend",function oe(){p.style.maxHeight="none",p.removeEventListener("transitionend",oe)})})}else{const q=p.scrollHeight;p.style.maxHeight=q+"px",requestAnimationFrame(()=>{p.style.maxHeight="0"})}})}),$e(t),Ue(t),t.dataset.routeWired||(t.dataset.routeWired="1",M.subscribe("router:afterChange",()=>t.__dmSetActive?.()));function x(){const r=location.hash||"#/";$(t).find(".sidebar-link").removeClass("active"),$(t).find(`.sidebar-link[data-url="${r}"]`).addClass("active")}x(),t.__dmSetActive=x}function Re(){try{document.dispatchEvent(new CustomEvent("dm:sidebar-rebuild"))}catch{}}function G(e){return`${location.origin}${location.pathname}${e}`}function N(e,n){const o=O();if(!Ae(o,e(U(o)))){E.toast("Frequent could not be saved - this browser is blocking storage.",{type:"error"});return}n&&E.toast(n,{type:"success",duration:2500}),Re()}function Ie(e){const n=e.dataset.url;if(!n)return[];const o=e.querySelector(".sidebar-text")?.textContent.trim()||n,t=U(O()),s=t.includes(n),d=!!e.closest("details[data-frequent]"),a=t.indexOf(n),i=!s&&t.length>=X;return[{header:o},{label:"Open",icon:"arrow-right",action:()=>{location.hash=n}},{label:"Open in a new tab",icon:"external-link",action:()=>window.open(G(n),"_blank","noopener")},{type:"divider"},s?{label:"Remove from Frequent",icon:"star",action:()=>N(u=>ke(u,n),`${o} removed from Frequent.`)}:{label:i?`Frequent is full (${X})`:"Add to Frequent",icon:"star",disabled:i,action:()=>N(u=>Se(u,n),`${o} added to Frequent.`)},...d?[{label:"Move up",icon:"arrow-up",disabled:a<=0,action:()=>N(u=>Z(u,n,-1))},{label:"Move down",icon:"arrow-down",disabled:a<0||a>=t.length-1,action:()=>N(u=>Z(u,n,1))}]:[],{type:"divider"},{label:"Copy link",icon:"link",action:async()=>{try{await navigator.clipboard.writeText(G(n)),E.toast("Link copied.",{type:"success",duration:2e3})}catch{E.toast(G(n),{type:"info",duration:8e3})}}}]}function Oe(e,n){const o=e.closest("details"),t=e.querySelector(".sidebar-text")?.textContent.trim()||"Folder",s=d=>n.querySelectorAll("details").forEach(a=>{a.open!==d&&(a.open=d)});return[{header:t},{label:o?.open?"Close":"Open",icon:o?.open?"chevron-up":"chevron-down",action:()=>{o&&(o.open=!o.open)}},{label:"Open every folder",icon:"chevrons-down",action:()=>s(!0)},{label:"Close every folder",icon:"chevrons-up",action:()=>s(!1)},...o?.hasAttribute("data-frequent")?[{type:"divider"},{label:"Clear Frequent",icon:"x",action:()=>N(()=>[],"Frequent cleared.")}]:[]]}function Ue(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")?Oe(n,e):Ie(n)}))}
|
|
1
|
+
import{api as b,getUser as N}from"../api.js";import{isPathAllowed as le}from"./admin-scope.js";import{colourToCss as K,applyFloat as ce,applyHighlight as de,itemHighlightAttrs as ue}from"/public/js/menu-decor.mjs?v=20261009-hover-darken";import{placeTools as me,ensureLockedTools as fe,managePluginsItem as pe,stripItemByUrl as ge,insertFoldersBeforeSystem as he,pruneEmptyToolsFolder as be,sidebarStateKey as V,canAny as _,filterByPermission as ye,MANAGE_PLUGINS_URL as X}from"./sidebar-grouping.js";import{arrangeButtonMarkup as ve,canArrange as xe,startArrange as $e}from"./sidebar-arrange.js";import{collapseButtonMarkup as we,wireSidebarSize as Se,COLLAPSED_WIDTH as ke}from"./sidebar-size.js";import{FREQUENT_MAX as Z,addFrequent as Ce,frequentFolder as qe,moveFrequent as Y,readFrequent as U,readFrequentHidden as Ae,removeFrequent as Le,writeFrequent as Fe,writeFrequentHidden as J}from"./sidebar-frequent.js";function h(e){return String(e??"").replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""")}const Ee=[{text:"Dashboard",url:"#/",icon:"home"},{text:"Menus",url:"#/menus",icon:"menu",permission:"menus"}];async function Te(e){if(!_(e,"projects"))return[];let n=[];try{n=await b.projects.listWithCounts()}catch{return[]}if(!Array.isArray(n))return[];const o=[{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:"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(t=>{const c="#/projects/"+encodeURIComponent(t.slug),a=t.counts||{},i=[{text:"Overview",url:c,icon:t.icon||"folder"}];for(const l of o){const d=Number(a[l.key])||0;d<=0||i.push({text:`${l.text} (${d})`,url:c+l.path,icon:l.icon})}i.push({text:"Settings",url:c+"/settings",icon:"settings"});const u=t.enabled===!1;return{text:t.name||t.slug,key:`project.${t.slug}`,icon:t.icon||"folder",items:i,...u&&{colour:"neutral",badge:{text:"off"}}}})}function Me(e,n){for(const o of e)if(!o.hidden&&(o.key==="projects"||!o.key&&(o.text||"").toLowerCase()==="projects")){o.items=Array.isArray(o.items)?o.items.slice():[],o.items.push(...n);return}}function Q(e,n){const o=te.includes(n.tone)?n.tone:"info",t=n.label||String(n.count);n.details?F.set(e,n.details):F.delete(e);const s=!n.inventory&&C.some(c=>c.url===e&&!c.inventory)?" data-live":"";return`<span class="sidebar-badge sidebar-badge--${o}" data-count-for="${h(e)}"${s} data-badge-label="${h(t)}" aria-label="${h(t)}">${h(String(n.count))}</span>`}function ee(e){for(const n of e.querySelectorAll("details")){const o=n.querySelector(":scope > summary");if(!o)continue;let t=0,s=!1;const c=[];for(const d of n.querySelectorAll(":scope > .sidebar-children .sidebar-badge[data-live]")){const m=Number(d.textContent)||0;m&&(t+=m,s||(s=d.classList.contains("sidebar-badge--danger")),c.push({text:d.closest("a")?.querySelector(".sidebar-text")?.textContent.trim()??"",meta:d.dataset.badgeLabel,tone:d.classList.contains("sidebar-badge--danger")?"danger":void 0}))}const a=`folder:${n.dataset.stateKey||o.textContent.trim()}`;let i=o.querySelector(":scope > .sidebar-badge--sum");if(!t){i?.remove(),F.delete(a);continue}i||(i=document.createElement("span"),o.querySelector(".sidebar-text")?.after(i));const u=o.querySelector(".sidebar-text")?.textContent.trim()||"This folder",l=c.map(d=>`${d.text}: ${d.meta}`).join(" \xB7 ");i.className=`sidebar-badge sidebar-badge--sum sidebar-badge--${s?"danger":"info"}`,i.dataset.countFor=a,i.dataset.badgeLabel=l,i.setAttribute("aria-label",l),i.textContent=String(t),F.set(a,{title:u,items:c})}}const F=new Map;function He(e,n){if(!e.url||!n)return"";const o=n[e.url];return!o||!(o.count>0)&&!o.showZero?"":Q(e.url,o)}const te=["info","attention","danger","live"],Ne={"#/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 z(e,{fallbackLabel:n="",tone:o="info"}={}){const t=Number(typeof e=="object"&&e!==null?e.count:e);if(!Number.isFinite(t)||t<0)return null;const s=typeof e?.label=="string"&&e.label||(n?`${t} - ${n}`:String(t)),c={count:Math.round(t),label:s,tone:te.includes(e?.tone)?e.tone:o};return e?.details&&typeof e.details=="object"&&(c.details=e.details),e?.inventory===!0&&(c.inventory=!0),e?.showZero===!0&&(c.showZero=!0),c}function ne(e,n,o=null){if(e&&e.type==="separator")return'<hr class="sidebar-divider">';if(e&&e.type==="spacer"){const g=["xs","sm","md","lg","xl","flex"].includes(e.size)?e.size:"md";return`<div class="sidebar-spacer sidebar-spacer--${h(g)}" aria-hidden="true"></div>`}const t=String(e.text||"?").trim().charAt(0).toUpperCase()||"?",s=e.icon?`<span data-icon="${h(e.icon)}"></span>`:`<span class="sidebar-initial" aria-hidden="true">${h(t)}</span>`,c=h(e.text||""),a=e.url?h(e.url):"",i=Array.isArray(e.items)&&e.items.length>0,u=e.colour?K(e.colour):"",l=ue(e),d=[l.hover&&` data-menu-item-hover="${h(l.hover)}"`,l.active&&` data-menu-item-active="${h(l.active)}"`].filter(Boolean).join(""),m=[u&&`color:${h(u)}`,l.accent&&`--dm-menu-highlight:${h(l.accent)}`].filter(Boolean).join(";"),y=m?` style="${m}"`:"";let v="";if(e.badge&&e.badge.text!=null&&e.badge.text!==""){const g=e.badge.variant?K(e.badge.variant):"";v=`<span class="dm-menu-badge"${g?` style="background:${h(g)};color:var(--dm-text-inverse,#fff)"`:""}>${h(String(e.badge.text))}</span>`}if(e.frequent&&e.empty){const g=V(e,o),k=S.get(g)!==!1;return`<details data-state-key="${h(g)}"${k?" open":""} data-frequent data-frequent-empty>
|
|
2
|
+
<summary${y}>${s} <span class="sidebar-text">${c}</span></summary>
|
|
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(i){const g=V(e,o),k=S.get(g)!==!1,q=e.items.map(T=>ne(T,n,g)).join(""),A=e.frequent?' data-frequent title="Your Frequent Tools - right-click any Tool to add or remove it"':"";return`<details data-state-key="${h(g)}"${k?" open":""}${A}>
|
|
5
|
+
<summary${y}>${s} <span class="sidebar-text">${c}</span>${v}</summary>
|
|
6
|
+
<div class="sidebar-children">${q}</div>
|
|
7
|
+
</details>`}return`<a href="${a}" class="sidebar-link" data-url="${a}"${y}${d}>${s} <span class="sidebar-text">${c}</span>${He(e,n)}${v}</a>`}async function je(e=[],n=[],o=null){const t=f=>f.then(x=>Array.isArray(x)?x.length:Array.isArray(x?.entries)?x.entries.length:0).catch(()=>0);C=Re(e,o);const s=new Set(C.map(f=>f.url)),c={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"},a=(f,x)=>_(n,f)&&!s.has(c[f])?x():Promise.resolve(0),[i,u,l,d,m,y,v,g,k,q,A,T,I,O]=await Promise.all([a("pages",()=>t(b.pages.list())),a("media",()=>t(b.media.list())),a("menus",()=>t(b.menus.list())),a("context-menus",()=>t(b.contextMenus.list())),a("theme",()=>b.theme.get().then(f=>(f?.overrides||[]).length).catch(()=>0)),a("collections",()=>t(b.collections.list())),a("forms",()=>t(b.forms.list())),a("views",()=>t(b.views.list())),a("actions",()=>t(b.actions.list())),a("blocks",()=>t(b.blocks.list())),a("components",()=>t(b.components.list())),a("users",()=>t(b.users.list())),a("plugins",()=>t(b.plugins.list())),a("notifications",()=>b.system?.notifications?.unreadCount?.().then(f=>f?.count??0).catch(()=>0)??Promise.resolve(0))]),B={};await Promise.all(C.map(async f=>{const x=await se(f);x&&(B[f.url]=x)}));const P={"#/pages":i,"#/media":u,"#/menus":l,"#/context-menus":d,"#/theme":m,"#/collections":y,"#/forms":v,"#/views":g,"#/actions":k,"#/blocks":q,"#/components":A,"#/users":T,"#/plugins":I,"#/system/notifications":O},R={};for(const[f,x]of Object.entries(P)){if(s.has(f))continue;const[r,w]=Ne[f]??["item","items"],p=z(x);p&&(R[f]={...p,label:`${p.count} ${p.count===1?r:w}`})}return{...R,...B}}let C=[];const Be="dm:sidebar-count",Pe=6e4;function Re(e=[],n=null){const o=[],t=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&&o.push({url:s.url,countUrl:s.countUrl,text:s.text||"",countLabel:s.countLabel||"",inventory:s.inventory===!0});for(const c of[...s.children||[],...s.items||[]])t(c)}};for(const s of e??[])t(s?.item??s);return o}async function se(e){try{const n=new Date,o=`${n.getFullYear()}-${String(n.getMonth()+1).padStart(2,"0")}-${String(n.getDate()).padStart(2,"0")}`,t=e.countUrl+(e.countUrl.includes("?")?"&":"?")+`today=${o}`;return z(await H.get(t),{fallbackLabel:e.countLabel||e.text})}catch{return null}}function oe(e,n,o){const t=[...e.querySelectorAll("a.sidebar-link")].filter(s=>s.dataset.url===n);if(t.length){for(const s of t){const c=s.querySelector(".sidebar-badge[data-count-for]");if(!o||!(o.count>0)&&!o.showZero)c?.remove();else{const a=document.createRange();a.selectNodeContents(s);const i=a.createContextualFragment(Q(n,o)).firstElementChild;c?c.replaceWith(i):s.querySelector(".sidebar-text")?.after(i)}}W.set(n,o),ee(e)}}const W=new Map;async function D(e,n=null){const o=n?C.filter(t=>t.url===n):C;await Promise.all(o.map(async t=>{const s=await se(t),c=W.get(t.url);JSON.stringify(c??null)!==JSON.stringify(s)&&oe(e,t.url,s)}))}function _e(e){e.dataset.liveCounts||(e.dataset.liveCounts="1",window.addEventListener(Be,n=>{const o=n.detail||{};if(o.url)if(o.count!==void 0){const t=C.find(s=>s.url===o.url);oe(e,o.url,z(o,{fallbackLabel:t?.countLabel||t?.text||""}))}else D(e,o.url)}),setInterval(()=>{!document.hidden&&document.body.contains(e)&&D(e)},Pe),document.addEventListener("visibilitychange",()=>{document.hidden||D(e)}),Ie(e))}function ie(e,n,o){const t=(a,i,u)=>{const l=document.createElement(a);return i&&(l.className=i),u!=null&&(l.textContent=String(u)),l};if(!o){e.textContent=n;return}e.appendChild(t("strong","dm-badge-pop-title",o.title||n)),(o.rows?.length||!o.items?.length)&&e.appendChild(t("p","dm-badge-pop-sub",n));const s=Array.isArray(o.rows)?o.rows.slice(0,6):[];if(s.length){const a=t("dl","dm-badge-pop-rows");for(const i of s)a.appendChild(t("dt",null,i.label)),a.appendChild(t("dd",i.tone==="danger"?"is-danger":null,i.value));e.appendChild(a)}const c=Array.isArray(o.items)?o.items.slice(0,6):[];if(c.length){const a=t("ul","dm-badge-pop-items");for(const i of c){const u=t("li",i.tone==="danger"?"is-danger":null);u.appendChild(t("span","dm-badge-pop-text",i.text)),i.meta&&u.appendChild(t("span","dm-badge-pop-meta",i.meta)),a.appendChild(u)}e.appendChild(a)}else o.empty&&e.appendChild(t("p","dm-badge-pop-empty",o.empty))}function Ie(e){let n=null,o=null;const t=()=>e.classList.contains("is-collapsed"),s=()=>{clearTimeout(o),n?.remove(),n=null},c=(l,d)=>{s(),o=setTimeout(()=>{n=document.createElement("div"),n.className="dm-help-pop dm-badge-pop",n.setAttribute("role","tooltip"),d(n),document.body.appendChild(n);const m=l.getBoundingClientRect(),y=n.getBoundingClientRect(),v=Math.min(m.right+10,innerWidth-y.width-8);n.style.left=`${Math.max(8,v)}px`,n.style.top=`${Math.max(8,Math.min(m.top+m.height/2-y.height/2,innerHeight-y.height-8))}px`},150)},a=l=>{const d=l?.dataset.badgeLabel;if(!d)return s();c(l,m=>ie(m,d,F.get(l.dataset.countFor)))},i=l=>{const d=l.querySelector(":scope > .sidebar-text")?.textContent.trim()||"",m=l.querySelector(":scope > .sidebar-badge[data-badge-label]:not([hidden])");c(l,y=>{const v=m&&F.get(m.dataset.countFor);if(v)return ie(y,m.dataset.badgeLabel,{...v,title:v.title||d});if(!m){y.textContent=d;return}const g=document.createElement("strong");g.className="dm-badge-pop-title",g.textContent=d;const k=document.createElement("p");k.className="dm-badge-pop-sub",k.textContent=m.dataset.badgeLabel,y.append(g,k)})},u=l=>{const d=l.closest?.("a.sidebar-link, summary");return d&&e.contains(d)?d:null};e.addEventListener("mouseover",l=>{if(t()){const m=u(l.target);m&&!m.contains(l.relatedTarget)&&i(m);return}const d=l.target.closest?.(".sidebar-badge[data-badge-label]");d&&a(d)}),e.addEventListener("mouseout",l=>{if(t()){const d=u(l.target);d&&!d.contains(l.relatedTarget)&&s();return}l.target.closest?.(".sidebar-badge[data-badge-label]")&&s()}),e.addEventListener("focusin",l=>{const d=l.target.closest?.("a, summary");if(t()&&d&&e.contains(d)&&d.querySelector(":scope > .sidebar-text"))return i(d);const m=d?.querySelector(":scope > .sidebar-badge[data-badge-label]:not([hidden])");m?a(m):s()}),e.addEventListener("focusout",s),e.addEventListener("click",s),window.addEventListener("scroll",s,!0),window.addEventListener("hashchange",s)}async function Oe(e=[]){if(!_(e,"settings"))return"Admin";try{const n=await b.settings.get();return n?.adminBrand?.title||n?.title||"Admin"}catch{return"Admin"}}export async function renderAdminSidebar({mount:e,permissions:n,adminScope:o=null}){const t=$(e).get(0);if(!t)return;let s,c=null,a=null,i=null,u=null,l=null;try{const r=await b.get("/sidebar/menu");s=Array.isArray(r?.items)?r.items:null,c=r?.variant||null,a=r?.position||null,i=r?.style||null,u=r?.float||null,l=r?.highlight||null}catch{s=null}(!s||!s.some(r=>r&&(!r.hidden||r.restricted)))&&(console.warn("[admin-sidebar] No admin-sidebar menu found; using fallback tree"),s=Ee.slice());let d=[];try{d=await H.get("/api/sidebar/registered-items")||[]}catch{}s=ge(s,X),s=me(s,d.filter(r=>(r?.item??r)?.url!==X)),s=fe(s),s=he(s,[pe()]);const m=await Te(n);m.length&&Me(s,m),s=ye(s,n,o),s=be(s);const y=qe(s,U(N()),{hidden:Ae(N())});y&&(s=[y,...s]);const[v,g]=await Promise.all([je(d.filter(r=>_(n,r?.item?.permission)),n,o).catch(()=>({})),Oe(n)]),k=c?` dm-admin-sidebar--${h(c)}`:"";function q(r,w="px"){if(r==null)return r;const p=String(r).trim();return/^\d+(\.\d+)?$/.test(p)?`${p}${w}`:p}let A="";if(i){const r=[];i.fontFamily&&r.push(`font-family: ${i.fontFamily}, sans-serif`),i.fontSize&&r.push(`font-size: ${q(i.fontSize)}`),i.fontWeight&&r.push(`font-weight: ${i.fontWeight}`),i.letterSpacing&&r.push(`letter-spacing: ${q(i.letterSpacing,"em")}`);const w=[];if(r.length&&w.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 { ${r.join("; ")} }`),i.iconSize){const p=q(i.iconSize);w.push(`#admin-sidebar .dm-admin-sidebar [data-icon], #admin-sidebar .dm-admin-sidebar [data-icon] svg { width: ${p} !important; height: ${p} !important; }`)}w.length&&(A=`<style data-admin-sidebar-style>${w.join(" ")}</style>`)}const T=xe(n)?ve():"",I=`<div class="dm-admin-sidebar-header"><span data-icon="layout"></span><span class="dm-admin-sidebar-title">${h(g)}</span>${T}${we()}</div>`,O=s.some(r=>r&&r.type==="spacer"&&r.size==="flex")?" dm-admin-sidebar--has-flex-spacer":"",B=`${A}<nav class="dm-admin-sidebar${k}${O}">${I}${s.map(r=>ne(r,v)).join("")}</nav>`,P=document.createRange();P.selectNodeContents(t);const R=P.createContextualFragment(B);t.replaceChildren(R),Domma.icons.scan(t),t.querySelector(".dm-admin-sidebar-arrange")?.addEventListener("click",()=>$e(t));for(const r of C)W.set(r.url,v[r.url]??null);ee(t),_e(t),t.removeAttribute("data-menu-hover"),t.removeAttribute("data-menu-active"),t.style.removeProperty("--dm-menu-highlight"),de(t,{highlight:l});const f=document.querySelector(".dashboard-wrapper");["position","top","bottom","left","right","zIndex","maxHeight","transform"].forEach(r=>{t.style[r]=""}),t.classList.remove("dm-menu-floating"),f&&(f.style.paddingLeft="",f.style.paddingRight=""),t.__dmGutter=()=>{if(a!=="floating"||!f)return;const r=u&&u.anchor||"TL",w=Number.isFinite(Number(u?.offsetX))?Number(u.offsetX):16,p=t.classList.contains("is-collapsed")?ke:parseInt(t.style.width,10)||t.getBoundingClientRect().width||260,L=Math.round(p+w*2)+"px";r==="R"||r==="TR"||r==="BR"?f.style.paddingRight=L:f.style.paddingLeft=L},a==="floating"&&ce(t,{position:"floating",float:u||{}}),t.querySelectorAll("details").forEach(r=>{r.addEventListener("toggle",function(){const w=this.getAttribute("data-state-key");w&&S.set(w,this.open);const p=this.querySelector(":scope > .sidebar-children");if(p)if(this.open){const L=p.scrollHeight;p.style.maxHeight="0",requestAnimationFrame(()=>{p.style.maxHeight=L+"px",p.addEventListener("transitionend",function re(){p.style.maxHeight="none",p.removeEventListener("transitionend",re)})})}else{const L=p.scrollHeight;p.style.maxHeight=L+"px",requestAnimationFrame(()=>{p.style.maxHeight="0"})}})}),Se(t),De(t),t.dataset.routeWired||(t.dataset.routeWired="1",M.subscribe("router:afterChange",()=>t.__dmSetActive?.()));function x(){const r=/^#\/(dashboards|my-dashboard)\b/.test(location.hash)?"#/":location.hash||"#/";$(t).find(".sidebar-link").removeClass("active"),$(t).find(`.sidebar-link[data-url="${r}"]`).addClass("active")}x(),t.__dmSetActive=x}function ae(){try{document.dispatchEvent(new CustomEvent("dm:sidebar-rebuild"))}catch{}}function G(e){return`${location.origin}${location.pathname}${e}`}function j(e,n){const o=N(),t=e(U(o));if(!Fe(o,t)){E.toast("Frequent could not be saved - this browser is blocking storage.",{type:"error"});return}t.length&&J(o,!1),n&&E.toast(n,{type:"success",duration:2500}),ae()}function Ue(e){if(!J(N(),e)){E.toast("Frequent could not be saved - this browser is blocking storage.",{type:"error"});return}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}),ae()}function ze(e){const n=e.dataset.url;if(!n)return[];const o=e.querySelector(".sidebar-text")?.textContent.trim()||n,t=U(N()),s=t.includes(n),c=!!e.closest("details[data-frequent]"),a=t.indexOf(n),i=!s&&t.length>=Z;return[{header:o},{label:"Open",icon:"arrow-right",action:()=>{location.hash=n}},{label:"Open in a new tab",icon:"external-link",action:()=>window.open(G(n),"_blank","noopener")},{type:"divider"},s?{label:"Remove from Frequent",icon:"star",action:()=>j(u=>Le(u,n),`${o} removed from Frequent.`)}:{label:i?`Frequent is full (${Z})`:"Add to Frequent",icon:"star",disabled:i,action:()=>j(u=>Ce(u,n),`${o} added to Frequent.`)},...c?[{label:"Move up",icon:"arrow-up",disabled:a<=0,action:()=>j(u=>Y(u,n,-1))},{label:"Move down",icon:"arrow-down",disabled:a<0||a>=t.length-1,action:()=>j(u=>Y(u,n,1))}]:[],{type:"divider"},{label:"Copy link",icon:"link",action:async()=>{try{await navigator.clipboard.writeText(G(n)),E.toast("Link copied.",{type:"success",duration:2e3})}catch{E.toast(G(n),{type:"info",duration:8e3})}}}]}function We(e,n){const o=e.closest("details"),t=e.querySelector(".sidebar-text")?.textContent.trim()||"Folder",s=c=>n.querySelectorAll("details").forEach(a=>{a.open!==c&&(a.open=c)});return[{header:t},{label:o?.open?"Close":"Open",icon:o?.open?"chevron-up":"chevron-down",action:()=>{o&&(o.open=!o.open)}},{label:"Open every folder",icon:"chevrons-down",action:()=>s(!0)},{label:"Close every folder",icon:"chevrons-up",action:()=>s(!1)},...o?.hasAttribute("data-frequent-empty")?[{type:"divider"},{label:"Hide Frequent until I add a Tool",icon:"eye-off",action:()=>Ue(!0)}]:o?.hasAttribute("data-frequent")?[{type:"divider"},{label:"Clear Frequent",icon:"x",action:()=>j(()=>[],"Frequent cleared.")}]:[]]}function De(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")?We(n,e):ze(n)}))}
|
|
@@ -12,11 +12,13 @@
|
|
|
12
12
|
"Needs attention" list gathered from every screen's sidebar badge. Every site has it, and it looks the same
|
|
13
13
|
for everyone.</p>
|
|
14
14
|
|
|
15
|
-
<h3>Dashboard Pro:
|
|
16
|
-
<p>With the <strong>Dashboard Pro</strong> Tool (from the Marketplace), the
|
|
17
|
-
<strong>
|
|
18
|
-
|
|
19
|
-
|
|
15
|
+
<h3>Dashboard Pro: Dashboards you build yourself</h3>
|
|
16
|
+
<p>With the <strong>Dashboard Pro</strong> Tool (from the Marketplace), the sidebar entry becomes
|
|
17
|
+
<strong>Dashboards</strong>: one place, as tabs. The first tab, <strong>Overview</strong>, is the Dashboard
|
|
18
|
+
above - always there, and it cannot be moved or removed. After it come your own <strong>boards</strong> of
|
|
19
|
+
<strong>widgets</strong>, which each person arranges for themselves. Where another Tool owns the Dashboard - the
|
|
20
|
+
fleet manager's, say - that is the Overview tab instead. Switch Dashboard Pro off and the entry goes back to
|
|
21
|
+
Dashboard at once; everyone's boards are kept for when it comes back.</p>
|
|
20
22
|
|
|
21
23
|
<h3>Adding widgets</h3>
|
|
22
24
|
<p>Choose <strong>Add widget</strong> in the banner, or right-click anywhere on the board. The gallery lists every
|
|
@@ -44,28 +46,30 @@
|
|
|
44
46
|
its own, and at once when its Tool's count changes.</p>
|
|
45
47
|
|
|
46
48
|
<h3>Several boards</h3>
|
|
47
|
-
<p>Keep up to twelve boards - Sales, Content, Support - as tabs
|
|
48
|
-
new one and opens the gallery for it. Right-click a tab (or double-click it to
|
|
49
|
-
move or delete it. Each board has its own widgets and arrangement, and
|
|
50
|
-
that opens.</p>
|
|
49
|
+
<p>Keep up to twelve boards - Sales, Content, Support - as tabs after the Overview. <strong>+</strong> (on any
|
|
50
|
+
Dashboards tab) makes a new one and opens the gallery for it. Right-click a board's tab (or double-click it to
|
|
51
|
+
rename) to rename, duplicate, move or delete it. Each board has its own widgets and arrangement, and its own
|
|
52
|
+
address, so you can bookmark it. The board you were last on is the one that opens from the boards.</p>
|
|
51
53
|
|
|
52
54
|
<h3>Settings (the cog)</h3>
|
|
53
55
|
<p>The cog in the banner holds your own settings:</p>
|
|
54
56
|
<ul>
|
|
55
|
-
<li><strong>Name</strong> - what your
|
|
57
|
+
<li><strong>Name</strong> - what your Dashboards are called, in the banner, the browser tab and the sidebar
|
|
58
|
+
entry. Each board's own name is its tab.</li>
|
|
56
59
|
<li><strong>Greeting</strong> - good morning, your name and today's date.</li>
|
|
57
60
|
<li><strong>Columns</strong> - 3, 4 or 6 on a wide screen. A narrow window always uses fewer.</li>
|
|
58
61
|
<li><strong>Cards</strong> - normal or compact.</li>
|
|
59
62
|
<li><strong>Updates</strong> - Live, Slower (three times as long between refreshes), or Paused (only when you
|
|
60
63
|
press Refresh).</li>
|
|
61
|
-
<li><strong>Open
|
|
64
|
+
<li><strong>Open on my boards</strong> - signing in, or pressing Dashboards in the sidebar, opens the board you
|
|
65
|
+
used last instead of the Overview. The Overview tab is always one click away.</li>
|
|
62
66
|
</ul>
|
|
63
67
|
|
|
64
68
|
<h3>For administrators</h3>
|
|
65
69
|
<ul>
|
|
66
70
|
<li><strong>The default</strong>: <em>Make this the default for everyone</em> in the banner makes the board you
|
|
67
71
|
are on the starting point for everyone who has not arranged their own. <em>Reset</em> takes anyone back to it.</li>
|
|
68
|
-
<li><strong>
|
|
72
|
+
<li><strong>Dashboards name</strong> (in the cog, under For everyone) - what Dashboards is called for everyone who
|
|
69
73
|
has not named their own.</li>
|
|
70
74
|
<li><strong>Lock the layout</strong> - everyone but administrators sees the site's default board and can fold
|
|
71
75
|
and refresh widgets but not add, move, resize or remove them. Turning the lock off gives people their own
|
|
@@ -34,6 +34,15 @@
|
|
|
34
34
|
<li>A new plugin arrives <strong>switched off</strong>. Switch it on when you are ready.</li>
|
|
35
35
|
<li>A paid plugin installed from Browse arrives licensed to this site. A plugin that is on
|
|
36
36
|
but has no valid licence does not run, and the Marketplace says so.</li>
|
|
37
|
+
<li><strong>Buying a paid Tool.</strong> On a site we host, a paid Tool you do not have yet shows
|
|
38
|
+
<strong>Buy…</strong> (or <strong>Renew…</strong> once its licence has run out). It opens
|
|
39
|
+
<strong>Manage my site</strong> in a new tab: we email you a sign-in link, the Tool is ticked
|
|
40
|
+
ready, and you see the new monthly price before you confirm. Once it is paid for, come back
|
|
41
|
+
and press <strong>Install</strong>. A site that shows <strong>Not licensed</strong> instead has no
|
|
42
|
+
online billing yet - ask us and we can connect it.</li>
|
|
43
|
+
<li><strong>Stopping a paid Tool.</strong> Uninstalling a paid Tool tells us. If you pay for it on
|
|
44
|
+
its own, it comes off your monthly bill and the rest of the month is credited; one that came
|
|
45
|
+
in a bundle or was added for you by us is sorted out by us, and we are told.</li>
|
|
37
46
|
<li>An update keeps the plugin's data. If it fails, the version you had is put back.</li>
|
|
38
47
|
<li>A plugin that needs a newer Domma CMS (its <code>minCmsVersion</code>) is refused, naming
|
|
39
48
|
both versions - update the CMS first. An update refused this way leaves the installed
|
|
@@ -20,6 +20,16 @@
|
|
|
20
20
|
group): the level-0 role and Admin have them all to begin with (Admin is given them once -
|
|
21
21
|
untick one and it stays unticked); other roles get them when you tick them.</p>
|
|
22
22
|
|
|
23
|
+
<h3>Frequent: your own shortcuts</h3>
|
|
24
|
+
<p><strong>Frequent</strong> is the folder at the top of the sidebar for the Tools you use most.
|
|
25
|
+
<strong>Right-click any Tool</strong> in the sidebar and choose <strong>Add to Frequent</strong>;
|
|
26
|
+
right-click it again for <strong>Move up</strong>, <strong>Move down</strong> or
|
|
27
|
+
<strong>Remove from Frequent</strong>. It holds up to twelve, with their count badges. Until you
|
|
28
|
+
add one it shows a short hint - right-click the folder and choose <strong>Hide Frequent until I
|
|
29
|
+
add a Tool</strong> if you would rather not see it. Frequent is yours alone: it is kept in this
|
|
30
|
+
browser, changes nobody else's sidebar, and only ever shows Tools you are allowed to open.
|
|
31
|
+
Right-clicking any folder also opens or closes it, or every folder at once.</p>
|
|
32
|
+
|
|
23
33
|
<h3>Contacts</h3>
|
|
24
34
|
<p>An address book for each user. <strong>Your contacts are private</strong>: nobody else sees
|
|
25
35
|
them, admins included. Star favourites, search as you type, and right-click a contact to edit,
|
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
import{apiRequest as m,getUser as
|
|
1
|
+
import{apiRequest as m,getUser as Le}from"../api.js";import{addBannerAction as C}from"../lib/plugin-chrome.js";import{helpPopovers as re}from"../lib/help-popover.js";import{openMenuAt as le}from"../lib/tool-kit.js";import{SIZE_LABELS as De,SIZES as Ce,addWidget as Oe,anyOpen as z,boardCards as Te,dropWidget as Me,galleryGroups as xe,removeWidget as ce,resizeWidget as qe,sameLayout as pe,setAllCollapsed as Ne,setCollapsed as Re,shiftWidget as G,withToday as Ie}from"../lib/dashboard-board-arrange.js";import{destroyWidget as ze,drawError as Ge,drawWidget as Be}from"../lib/widget-render.js";import{MAX_BOARDS as be,OVERVIEW as B,boardFromHash as ue,boardHref as Fe,dashboardsLabel as _,stripTabs as Pe,tabsMarkup as We}from"../lib/dashboards-arrange.js";import{allowOverviewOnce as Ye,setDashboardsBanner as He}from"../lib/dashboards-strip.js";const Ue=400,Je=h=>String(h||"").replace(/^\/api(?=\/)/,""),r=h=>String(h??"").replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""");export async function mountBoard(h,f,{isAdmin:j=!1}={}){const F=h.get(0),c=F.querySelector(".dbx")||F;c.classList.add("dbp"),c.replaceChildren(),c.insertAdjacentHTML("beforeend",`
|
|
2
|
+
<div class="dbp-tabs" role="tablist" aria-label="Dashboards" hidden></div>
|
|
2
3
|
<p class="dbp-greeting" hidden></p>
|
|
3
4
|
<div class="dbp-head">
|
|
4
|
-
<p class="dbp-hint">Your own
|
|
5
|
-
<span data-help-title="
|
|
5
|
+
<p class="dbp-hint">Your own boards.
|
|
6
|
+
<span data-help-title="Dashboards" data-help="The first tab, Overview, is the site's Dashboard - always there. Your boards follow it: add widgets from the Tools you use (Add widget in the banner, or right-click the board), and each card keeps itself up to date. + makes a board; right-click a tab to rename, duplicate, move or delete it. Your boards are yours - other people arrange their own. Reset goes back to the site's default; an administrator can make a board the default for everyone who has not arranged their own."></span></p>
|
|
6
7
|
<span class="dbp-source" aria-live="polite"></span>
|
|
7
8
|
</div>
|
|
8
|
-
<div class="dbp-tabs" role="tablist" aria-label="Your boards" hidden></div>
|
|
9
9
|
<div class="dbp-board" role="list" aria-label="Dashboard widgets"></div>
|
|
10
10
|
<div class="dbp-empty-board" hidden>
|
|
11
11
|
<p>Nothing on your Dashboard yet.</p>
|
|
12
12
|
<button type="button" class="btn btn-primary dbp-add-first"><span data-icon="plus" data-icon-size="14"></span> Add a widget</button>
|
|
13
|
-
</div>`);const b=c.querySelector(".dbp-board"),
|
|
13
|
+
</div>`);const b=c.querySelector(".dbp-board"),he=c.querySelector(".dbp-source"),V=c.querySelector(".dbp-greeting"),fe=c.querySelector(".dbp-hint"),$=c.querySelector(".dbp-tabs"),K=()=>c.isConnected;let y=[],i={version:1,widgets:[]},o={version:2,active:"main",boards:[{id:"main",name:"Main",widgets:[]}]},q="",O="built-in",d={mine:{name:"",greeting:!0,columns:4,density:"normal",updates:"live",home:!1},site:{name:"Dashboards",locked:!1},name:"Dashboards",isAdmin:j,locked:!1};const T=new Map;f.add(()=>{for(const e of T.values())clearInterval(e);T.clear()});async function me(){const[e,t,a]=await Promise.all([m("/widgets",{method:"GET"}).catch(()=>[]),m("/dashboard/boards",{method:"GET"}).catch(()=>null),m("/dashboard/settings",{method:"GET"}).catch(()=>null)]);a&&(d={...d,...a}),y=Array.isArray(e)?e:[],t?.boards?.length&&(o={version:2,active:t.active,boards:t.boards});const s=ue(location.hash).board;s&&o.boards.some(n=>n.id===s)&&(o.active=s),i={version:1,widgets:w().widgets},q=JSON.stringify(o),O=t?.source||"built-in"}const w=()=>o.boards.find(e=>e.id===o.active)||o.boards[0],N=()=>{w().widgets=i.widgets};let P=null;function A(){clearTimeout(P),!d.locked&&(P=setTimeout(async()=>{N();const e=JSON.stringify(o);if(e!==q)try{const t=await m("/dashboard/boards",{method:"PUT",body:e});q=e,O=t?.source||"mine",W()}catch(t){E.toast(`Your layout was not saved: ${t.message||t}`,{type:"error"})}},Ue))}f.add(()=>clearTimeout(P));function W(){he.textContent={mine:"Your layout",site:"The site's default layout","built-in":"The starting layout",locked:"Set by your administrator"}[O]||""}function g(e){pe(e,i)||(i=e,S(),A())}function Y(e,t){i=Re(i,e,t);const a=b.querySelector(`.dbp-card[data-id="${CSS.escape(e)}"]`);a&&X(a,t),A()}function X(e,t){const a=e.classList.contains("is-collapsed");e.classList.toggle("is-collapsed",t);const s=e.querySelector(".dbp-body");s&&(s.hidden=t);const n=e.querySelector(".dbp-fold"),l=e.getAttribute("aria-label")||"";n&&(n.setAttribute("aria-expanded",t?"false":"true"),n.title=t?"Open":"Fold",n.setAttribute("aria-label",`${t?"Open":"Fold"} ${l}`)),a&&!t&&v(e)}function Z(e){i=Ne(i,e),b.querySelectorAll(".dbp-card").forEach(t=>X(t,e)),A()}const ge=4,ye=16;function Q(e){const t=e.getBoundingClientRect().height;t&&(e.style.gridRowEnd=`span ${Math.max(1,Math.ceil((t+ye)/ge))}`)}const H=typeof ResizeObserver=="function"?new ResizeObserver(e=>{for(const t of e)Q(t.target)}):null;f.add(()=>H?.disconnect());async function v(e){const t=y.find(s=>s.id===e.dataset.id),a=e.querySelector(".dbp-body");if(!(!t||!a)){e.classList.add("is-loading");try{const s=t.kind==="count"?Ie(t.dataUrl):t.dataUrl,n=await m(Je(s),{method:"GET"});if(!K()||!e.isConnected)return;await Be(a,t.kind,n)}catch(s){e.isConnected&&Ge(a,s.status===403?"You cannot see this any more.":"Could not load.")}finally{e.classList.remove("is-loading")}}}function ve(e){const t=e.href?`<a class="dbp-title" href="${r(e.href)}" title="Open ${r(e.title)}">${r(e.title)}</a>`:`<span class="dbp-title">${r(e.title)}</span>`;return`<section class="dbp-card dbp-size-${r(e.size)}${e.collapsed?" is-collapsed":""}" data-id="${r(e.id)}" role="listitem" tabindex="0" aria-label="${r(e.title)}">
|
|
14
14
|
<header class="dbp-card-head">
|
|
15
15
|
<span class="dbp-grip" title="Drag to move" aria-hidden="true"><span data-icon="menu" data-icon-size="14"></span></span>
|
|
16
16
|
<span class="dbp-icon" aria-hidden="true"><span data-icon="${r(e.icon||"grid")}" data-icon-size="16"></span></span>
|
|
@@ -20,44 +20,43 @@ import{apiRequest as m,getUser as ke}from"../api.js";import{addBannerAction as A
|
|
|
20
20
|
<button type="button" class="dbp-kebab" aria-label="Actions for ${r(e.title)}" title="Actions">\u22EE</button>
|
|
21
21
|
</header>
|
|
22
22
|
<div class="dbp-body"${e.collapsed?" hidden":""}><div class="dbp-skeleton"></div></div>
|
|
23
|
-
</section>`}function
|
|
24
|
-
aria-selected="${e.id===o.active}" data-board="${r(e.id)}" title="${r(e.name)} - right-click to rename, duplicate or delete">${r(e.name)}</button>`).join("")+(o.boards.length<12?'<button type="button" class="dbp-tab dbp-tab-new" data-new-board title="New board" aria-label="New board"><span data-icon="plus" data-icon-size="14"></span></button>':""),I.scan(w)}function _(e){e===o.active||!o.boards.some(t=>t.id===e)||(N(),o.active=e,i={version:1,widgets:T().widgets},$(),k(),S())}const V=()=>`b-${Date.now().toString(36)}${Math.random().toString(36).slice(2,5)}`;async function X(e,t=""){const a=await E.prompt(e,{inputValue:r(t),inputPlaceholder:"Sales, Content, Support\u2026"});return a==null?null:String(a).replace(/\s+/g," ").trim().slice(0,40)||null}async function Z(){if(o.boards.length>=12)return;const e=await X("Name the new board");if(!e)return;N();const t={id:V(),name:e,widgets:[]};o.boards.push(t),o.active=t.id,i={version:1,widgets:[]},$(),k(),S(),x()}async function C(e,t){const a=o.boards.findIndex(n=>n.id===e);if(a<0)return;N();const s=o.boards[a];if(t==="rename"){const n=await X("Rename the board",s.name);if(!n)return;s.name=n}else if(t==="duplicate"){if(o.boards.length>=12)return;const n={id:V(),name:`${s.name} copy`.slice(0,40),widgets:s.widgets.map(l=>({...l}))};o.boards.splice(a+1,0,n),o.active=n.id}else if(t==="delete"){if(o.boards.length<2||!await E.confirm(`Delete the board "${r(s.name)}"? Its widgets go with it (they stay available to add again).`))return;o.boards.splice(a,1),o.active===e&&(o.active=o.boards[Math.max(0,a-1)].id)}else t==="left"&&a>0?[o.boards[a-1],o.boards[a]]=[o.boards[a],o.boards[a-1]]:t==="right"&&a<o.boards.length-1&&([o.boards[a+1],o.boards[a]]=[o.boards[a],o.boards[a+1]]);i={version:1,widgets:T().widgets},$(),k(),S()}w.addEventListener("click",e=>{if(e.target.closest("[data-new-board]")){Z();return}const t=e.target.closest("[data-board]");t&&_(t.dataset.board)}),w.addEventListener("dblclick",e=>{const t=e.target.closest("[data-board]");t&&C(t.dataset.board,"rename")});const fe=E.contextMenu(w,{cursor:"glyph",match:"[data-board]",inherit:!1,density:"compact",items:e=>{const t=e.dataset.board,a=o.boards.findIndex(s=>s.id===t);return[{header:o.boards[a]?.name||"Board"},{label:"Open",icon:"arrow-right",disabled:t===o.active,action:()=>_(t)},{label:"Rename\u2026",icon:"edit",action:()=>C(t,"rename")},{label:"Duplicate",icon:"copy",disabled:o.boards.length>=12,action:()=>C(t,"duplicate")},{type:"divider"},{label:"Move left",icon:"arrow-left",disabled:a<=0,action:()=>C(t,"left")},{label:"Move right",icon:"arrow-right",disabled:a>=o.boards.length-1,action:()=>C(t,"right")},{type:"divider"},{label:"Delete\u2026",icon:"trash",danger:!0,disabled:o.boards.length<2,action:()=>C(t,"delete")}]}});f.add(()=>fe?.destroy?.());function k(){for(const t of M.values())clearInterval(t);M.clear(),b.querySelectorAll(".dbp-body").forEach(qe);const e=Ce(i,y);b.innerHTML=e.map(he).join(""),c.querySelector(".dbp-empty-board").hidden=e.length>0,W?.disconnect();for(const t of b.querySelectorAll(".dbp-card"))K(t),W?.observe(t);I.scan(b);for(const t of b.querySelectorAll(".dbp-card")){t.classList.contains("is-collapsed")||v(t);const a=y.find(n=>n.id===t.dataset.id);if(d.mine.updates==="paused")continue;const s=(a?.refresh||60)*1e3*(d.mine.updates==="slower"?3:1);M.set(t.dataset.id,setInterval(()=>{!document.hidden&&t.isConnected&&!t.classList.contains("is-collapsed")&&v(t)},s))}B()}const Q=e=>{const t=e.detail?.url;if(t)for(const a of b.querySelectorAll(".dbp-card"))y.find(n=>n.id===a.dataset.id)?.href===t&&v(a)};window.addEventListener("dm:sidebar-count",Q),f.add(()=>window.removeEventListener("dm:sidebar-count",Q));function me(e){const t=e.dataset.id,a=y.find(p=>p.id===t),s=i.widgets.find(p=>p.id===t);if(!a||!s)return[];const n=i.widgets.findIndex(p=>p.id===t),l=[{header:a.title},...a.href?[{label:`Open ${a.title}`,icon:"arrow-right",action:()=>{location.hash=a.href}}]:[],{label:s.collapsed?"Open":"Fold",icon:s.collapsed?"chevron-down":"chevron-up",action:()=>F(t,!s.collapsed)},{label:"Refresh",icon:"refresh-cw",disabled:s.collapsed===!0,action:()=>v(e)}];return d.locked?l:[{header:a.title},{label:"Add widget\u2026",icon:"plus",action:()=>x()},{type:"divider"},...a.href?[{label:`Open ${a.title}`,icon:"arrow-right",action:()=>{location.hash=a.href}}]:[],{label:s.collapsed?"Open":"Fold",icon:s.collapsed?"chevron-down":"chevron-up",action:()=>F(t,!s.collapsed)},{label:"Refresh",icon:"refresh-cw",disabled:s.collapsed===!0,action:()=>v(e)},{type:"divider"},...Ee.map(p=>({label:Se[p],icon:s.size===p?"check":"maximize-2",disabled:s.size===p,action:()=>g(De(i,t,p))})),{type:"divider"},{label:"Move earlier",icon:"arrow-left",disabled:n<=0,action:()=>g(z(i,t,-1))},{label:"Move later",icon:"arrow-right",disabled:n>=i.widgets.length-1,action:()=>g(z(i,t,1))},{type:"divider"},{label:"Remove from the Dashboard",icon:"x",action:()=>g(de(i,t))}]}const ge=E.contextMenu(b,{cursor:"glyph",match:".dbp-card",inherit:!1,density:"compact",items:e=>me(e)});f.add(()=>ge?.destroy?.());const ee=e=>{const t=e.target.closest(".dbp-kebab");if(t){ie(t);return}const a=e.target.closest(".dbp-fold");if(a){const s=a.closest(".dbp-card");s&&F(s.dataset.id,!s.classList.contains("is-collapsed"))}},te=e=>{const t=e.target.closest(".dbp-card");if(t){if(e.key==="F10"&&e.shiftKey){e.preventDefault(),ie(t);return}e.target===t&&(e.altKey&&(e.key==="ArrowLeft"||e.key==="ArrowUp")?(e.preventDefault(),g(z(i,t.dataset.id,-1)),ae(t.dataset.id)):e.altKey&&(e.key==="ArrowRight"||e.key==="ArrowDown")?(e.preventDefault(),g(z(i,t.dataset.id,1)),ae(t.dataset.id)):e.key==="Delete"&&(e.preventDefault(),g(de(i,t.dataset.id))))}},ae=e=>requestAnimationFrame(()=>b.querySelector(`.dbp-card[data-id="${CSS.escape(e)}"]`)?.focus());if(b.addEventListener("click",ee),b.addEventListener("keydown",te),f.add(()=>{b.removeEventListener("click",ee),b.removeEventListener("keydown",te)}),typeof E.sortable=="function"){const e=E.sortable(b,{items:".dbp-card",handle:".dbp-grip",live:!1,axis:"x",onDrop({key:t,targetKey:a,zone:s}){if(d.locked||s!=="before"&&s!=="after")return!1;const n=Le(i,t,a,s);return re(n,i)?!1:(g(n),!0)}});f.add(()=>e?.destroy?.())}function x(){const e=E.slideover({title:"Add a widget",size:"md",position:"right"}),t=document.createElement("div");t.className="dbp-gallery",t.innerHTML=`<input type="search" class="form-input dbp-gallery-search" placeholder="Search widgets" aria-label="Search widgets">
|
|
23
|
+
</section>`}function k(){$.hidden=!1;const e=d.locked===!0,t=Pe(o,e?"main":o.active,{locked:e,lockedName:_(d.name)});$.innerHTML=We(t,{canAdd:!e&&o.boards.length<be}),I.scan($);const a=_(d.name),s=t.find(n=>n.active)?.name||w().name;He(F,a,s),document.title=`${s} - ${a} - Domma CMS`}function M(){const e=Fe(o.active);if(location.hash!==e)try{history.replaceState(null,"",e)}catch{}}function U(e){if(e===B){Ye(),location.hash="#/";return}e===o.active||!o.boards.some(t=>t.id===e)||(N(),o.active=e,i={version:1,widgets:w().widgets},k(),M(),S(),A())}const ee=()=>`b-${Date.now().toString(36)}${Math.random().toString(36).slice(2,5)}`;async function te(e,t=""){const a=await E.prompt(e,{inputValue:r(t),inputPlaceholder:"Sales, Content, Support\u2026"});return a==null?null:String(a).replace(/\s+/g," ").trim().slice(0,40)||null}async function J(){if(o.boards.length>=12)return;const e=await te("Name the new board");if(!e)return;N();const t={id:ee(),name:e,widgets:[]};o.boards.push(t),o.active=t.id,i={version:1,widgets:[]},k(),M(),S(),A(),x()}async function L(e,t){const a=o.boards.findIndex(n=>n.id===e);if(a<0)return;N();const s=o.boards[a];if(t==="rename"){const n=await te("Rename the board",s.name);if(!n)return;s.name=n}else if(t==="duplicate"){if(o.boards.length>=12)return;const n={id:ee(),name:`${s.name} copy`.slice(0,40),widgets:s.widgets.map(l=>({...l}))};o.boards.splice(a+1,0,n),o.active=n.id,M()}else if(t==="delete"){if(o.boards.length<2||!await E.confirm(`Delete the board "${r(s.name)}"? Its widgets go with it (they stay available to add again).`))return;o.boards.splice(a,1),o.active===e&&(o.active=o.boards[Math.max(0,a-1)].id),M()}else t==="left"&&a>0?[o.boards[a-1],o.boards[a]]=[o.boards[a],o.boards[a-1]]:t==="right"&&a<o.boards.length-1&&([o.boards[a+1],o.boards[a]]=[o.boards[a],o.boards[a+1]]);i={version:1,widgets:w().widgets},k(),S(),A()}$.addEventListener("click",e=>{if(e.target.closest("[data-new-board]")){J();return}const t=e.target.closest("[data-board]");t&&U(t.dataset.board)}),$.addEventListener("dblclick",e=>{const t=e.target.closest("[data-board]");t&&t.dataset.board!==B&&!d.locked&&L(t.dataset.board,"rename")});const we=E.contextMenu($,{cursor:"glyph",match:"[data-board]",inherit:!1,density:"compact",items:e=>{const t=e.dataset.board,a=o.boards.findIndex(s=>s.id===t);return t===B||d.locked?[{header:e.textContent.trim()||"Overview"},{label:"Open",icon:"arrow-right",disabled:t!==B,action:()=>U(t)}]:[{header:o.boards[a]?.name||"Board"},{label:"Open",icon:"arrow-right",disabled:t===o.active,action:()=>U(t)},{label:"Rename\u2026",icon:"edit",action:()=>L(t,"rename")},{label:"Duplicate",icon:"copy",disabled:o.boards.length>=12,action:()=>L(t,"duplicate")},{type:"divider"},{label:"Move left",icon:"arrow-left",disabled:a<=0,action:()=>L(t,"left")},{label:"Move right",icon:"arrow-right",disabled:a>=o.boards.length-1,action:()=>L(t,"right")},{type:"divider"},{label:"Delete\u2026",icon:"trash",danger:!0,disabled:o.boards.length<2,action:()=>L(t,"delete")}]}});f.add(()=>we?.destroy?.());function S(){for(const t of T.values())clearInterval(t);T.clear(),b.querySelectorAll(".dbp-body").forEach(ze);const e=Te(i,y);b.innerHTML=e.map(ve).join(""),c.querySelector(".dbp-empty-board").hidden=e.length>0,H?.disconnect();for(const t of b.querySelectorAll(".dbp-card"))Q(t),H?.observe(t);I.scan(b);for(const t of b.querySelectorAll(".dbp-card")){t.classList.contains("is-collapsed")||v(t);const a=y.find(n=>n.id===t.dataset.id);if(d.mine.updates==="paused")continue;const s=(a?.refresh||60)*1e3*(d.mine.updates==="slower"?3:1);T.set(t.dataset.id,setInterval(()=>{!document.hidden&&t.isConnected&&!t.classList.contains("is-collapsed")&&v(t)},s))}W()}const ae=e=>{const t=e.detail?.url;if(t)for(const a of b.querySelectorAll(".dbp-card"))y.find(n=>n.id===a.dataset.id)?.href===t&&v(a)};window.addEventListener("dm:sidebar-count",ae),f.add(()=>window.removeEventListener("dm:sidebar-count",ae));function ke(e){const t=e.dataset.id,a=y.find(p=>p.id===t),s=i.widgets.find(p=>p.id===t);if(!a||!s)return[];const n=i.widgets.findIndex(p=>p.id===t),l=[{header:a.title},...a.href?[{label:`Open ${a.title}`,icon:"arrow-right",action:()=>{location.hash=a.href}}]:[],{label:s.collapsed?"Open":"Fold",icon:s.collapsed?"chevron-down":"chevron-up",action:()=>Y(t,!s.collapsed)},{label:"Refresh",icon:"refresh-cw",disabled:s.collapsed===!0,action:()=>v(e)}];return d.locked?l:[{header:a.title},{label:"Add widget\u2026",icon:"plus",action:()=>x()},{type:"divider"},...a.href?[{label:`Open ${a.title}`,icon:"arrow-right",action:()=>{location.hash=a.href}}]:[],{label:s.collapsed?"Open":"Fold",icon:s.collapsed?"chevron-down":"chevron-up",action:()=>Y(t,!s.collapsed)},{label:"Refresh",icon:"refresh-cw",disabled:s.collapsed===!0,action:()=>v(e)},{type:"divider"},...Ce.map(p=>({label:De[p],icon:s.size===p?"check":"maximize-2",disabled:s.size===p,action:()=>g(qe(i,t,p))})),{type:"divider"},{label:"Move earlier",icon:"arrow-left",disabled:n<=0,action:()=>g(G(i,t,-1))},{label:"Move later",icon:"arrow-right",disabled:n>=i.widgets.length-1,action:()=>g(G(i,t,1))},{type:"divider"},{label:"Remove from the Dashboard",icon:"x",action:()=>g(ce(i,t))}]}const Ee=E.contextMenu(b,{cursor:"glyph",match:".dbp-card",inherit:!1,density:"compact",items:e=>ke(e)});f.add(()=>Ee?.destroy?.());const se=e=>{const t=e.target.closest(".dbp-kebab");if(t){le(t);return}const a=e.target.closest(".dbp-fold");if(a){const s=a.closest(".dbp-card");s&&Y(s.dataset.id,!s.classList.contains("is-collapsed"))}},oe=e=>{const t=e.target.closest(".dbp-card");if(t){if(e.key==="F10"&&e.shiftKey){e.preventDefault(),le(t);return}e.target===t&&(e.altKey&&(e.key==="ArrowLeft"||e.key==="ArrowUp")?(e.preventDefault(),g(G(i,t.dataset.id,-1)),ne(t.dataset.id)):e.altKey&&(e.key==="ArrowRight"||e.key==="ArrowDown")?(e.preventDefault(),g(G(i,t.dataset.id,1)),ne(t.dataset.id)):e.key==="Delete"&&(e.preventDefault(),g(ce(i,t.dataset.id))))}},ne=e=>requestAnimationFrame(()=>b.querySelector(`.dbp-card[data-id="${CSS.escape(e)}"]`)?.focus());if(b.addEventListener("click",se),b.addEventListener("keydown",oe),f.add(()=>{b.removeEventListener("click",se),b.removeEventListener("keydown",oe)}),typeof E.sortable=="function"){const e=E.sortable(b,{items:".dbp-card",handle:".dbp-grip",live:!1,axis:"x",onDrop({key:t,targetKey:a,zone:s}){if(d.locked||s!=="before"&&s!=="after")return!1;const n=Me(i,t,a,s);return pe(n,i)?!1:(g(n),!0)}});f.add(()=>e?.destroy?.())}function x(){const e=E.slideover({title:"Add a widget",size:"md",position:"right"}),t=document.createElement("div");t.className="dbp-gallery",t.innerHTML=`<input type="search" class="form-input dbp-gallery-search" placeholder="Search widgets" aria-label="Search widgets">
|
|
25
24
|
<p class="dbp-gallery-hint">Widgets from the Tools you can open. Each one is added at its usual size - resize it from its \u22EE menu.</p>
|
|
26
|
-
<div class="dbp-gallery-list"></div>`,e.element.appendChild(t);const a=t.querySelector(".dbp-gallery-list"),s=t.querySelector(".dbp-gallery-search");function n(){const l=
|
|
25
|
+
<div class="dbp-gallery-list"></div>`,e.element.appendChild(t);const a=t.querySelector(".dbp-gallery-list"),s=t.querySelector(".dbp-gallery-search");function n(){const l=xe(y,i,s.value);a.innerHTML=l.length?l.map(p=>`
|
|
27
26
|
<h4 class="dbp-gallery-group">${r(p.source)}</h4>
|
|
28
27
|
${p.widgets.map(u=>`
|
|
29
28
|
<div class="dbp-gallery-item">
|
|
30
29
|
<span class="dbp-icon"><span data-icon="${r(u.icon||"grid")}" data-icon-size="16"></span></span>
|
|
31
30
|
<span class="dbp-gallery-main"><strong>${r(u.title)}</strong><small>${r(u.description||"")}</small></span>
|
|
32
31
|
${u.added?'<span class="dbp-gallery-added">On the board</span>':`<button type="button" class="btn btn-sm btn-primary" data-add="${r(u.id)}">Add</button>`}
|
|
33
|
-
</div>`).join("")}`).join(""):'<p class="dbp-empty">No widgets match.</p>',I.scan(a)}a.addEventListener("click",l=>{const p=l.target.closest("[data-add]")?.dataset.add,u=p&&y.find(
|
|
32
|
+
</div>`).join("")}`).join(""):'<p class="dbp-empty">No widgets match.</p>',I.scan(a)}a.addEventListener("click",l=>{const p=l.target.closest("[data-add]")?.dataset.add,u=p&&y.find(R=>R.id===p);u&&(g(Oe(i,u)),E.toast(`${u.title} added.`,{type:"success",duration:2e3}),n())}),s.addEventListener("input",n),e.open(),n(),setTimeout(()=>s.focus(),50)}c.querySelector(".dbp-add-first").addEventListener("click",x);function Se(){const e=new Date().getHours(),t=e<12?"Good morning":e<18?"Good afternoon":"Good evening",a=String(Le()?.name||"").trim().split(/\s+/)[0],s=new Date().toLocaleDateString("en-GB",{weekday:"long",day:"numeric",month:"long"});return`${t}${a?`, ${a}`:""}. ${s}.`}function ie(){const e=d.mine;k(),V.hidden=!e.greeting,V.textContent=e.greeting?Se():"",c.classList.remove("dbp-cols-3","dbp-cols-4","dbp-cols-6"),c.classList.add(`dbp-cols-${e.columns||4}`),c.classList.toggle("is-compact",e.density==="compact"),c.classList.toggle("is-locked",d.locked===!0),fe.firstChild.textContent=d.locked?"Your administrator has set this layout. You can fold and refresh widgets; the cog has your own settings. ":"Your own boards. Right-click the board to add widgets; drag a card by its grip; the chevron folds it; a card's \u22EE resizes or removes it. Overview, the first tab, is the site's Dashboard. ",d.locked&&(O="locked"),W()}const D=(e,t,a)=>`<option value="${r(e)}"${String(a)===String(e)?" selected":""}>${r(t)}</option>`;function de(){const e=d.mine,t=E.slideover({title:"Dashboard settings",size:"md",position:"right"}),a=document.createElement("form");a.className="dbp-settings",a.noValidate=!0,a.innerHTML=`
|
|
34
33
|
<h4 class="dbp-settings-section">Yours</h4>
|
|
35
34
|
<label class="dbp-field"><span class="form-label">Name
|
|
36
|
-
<span data-help="What your
|
|
37
|
-
<input class="form-input" name="name" maxlength="40" placeholder="${r(d.site.name
|
|
35
|
+
<span data-help="What your Dashboards are called - in the banner and the sidebar entry. Leave it empty to use the site's name. Each board's own name is its tab: right-click a tab to rename it."></span></span>
|
|
36
|
+
<input class="form-input" name="name" maxlength="40" placeholder="${r(_(d.site.name))}" value="${r(e.name)}" autocomplete="off"></label>
|
|
38
37
|
<label class="dbp-field dbp-switch"><input type="checkbox" class="form-switch" name="greeting"${e.greeting?" checked":""}>
|
|
39
38
|
<span>Greeting <span data-help="A line at the top: good morning, your name and today's date."></span></span></label>
|
|
40
39
|
<div class="dbp-field-row">
|
|
41
40
|
<label class="dbp-field"><span class="form-label">Columns
|
|
42
41
|
<span data-help="How many columns the board has on a wide screen. More columns fit more small widgets side by side; a narrow window always uses fewer."></span></span>
|
|
43
|
-
<select class="form-select" name="columns">${[3,4,6].map(n=>
|
|
42
|
+
<select class="form-select" name="columns">${[3,4,6].map(n=>D(n,`${n} columns`,e.columns)).join("")}</select></label>
|
|
44
43
|
<label class="dbp-field"><span class="form-label">Cards
|
|
45
44
|
<span data-help="Compact cards use less padding and smaller figures, so more fits on the screen."></span></span>
|
|
46
|
-
<select class="form-select" name="density">${
|
|
45
|
+
<select class="form-select" name="density">${D("normal","Normal",e.density)}${D("compact","Compact",e.density)}</select></label>
|
|
47
46
|
</div>
|
|
48
47
|
<label class="dbp-field"><span class="form-label">Updates
|
|
49
48
|
<span data-help="Live: each widget refreshes on its own interval. Slower: three times as long between refreshes - kinder to a slow connection. Paused: only when you press Refresh. A Tool saying its count changed still updates its widget."></span></span>
|
|
50
|
-
<select class="form-select" name="updates">${
|
|
49
|
+
<select class="form-select" name="updates">${D("live","Live",e.updates)}${D("slower","Slower",e.updates)}${D("paused","Paused - refresh by hand",e.updates)}</select></label>
|
|
51
50
|
<label class="dbp-field dbp-switch"><input type="checkbox" class="form-switch" name="home"${e.home?" checked":""}>
|
|
52
|
-
<span>Open
|
|
51
|
+
<span>Open on my boards <span data-help="Signing in, or pressing Dashboards in the sidebar, opens the board you used last instead of the Overview. The Overview tab is always one click away. Takes effect the next time the admin loads."></span></span></label>
|
|
53
52
|
${d.isAdmin?`
|
|
54
53
|
<h4 class="dbp-settings-section">For everyone</h4>
|
|
55
|
-
<label class="dbp-field"><span class="form-label">
|
|
56
|
-
<span data-help="What
|
|
57
|
-
<input class="form-input" name="siteName" maxlength="40" placeholder="
|
|
54
|
+
<label class="dbp-field"><span class="form-label">Dashboards name
|
|
55
|
+
<span data-help="What Dashboards is called - banner and sidebar - for everyone who has not named their own. Empty: Dashboards."></span></span>
|
|
56
|
+
<input class="form-input" name="siteName" maxlength="40" placeholder="Dashboards" value="${r(d.site.name)}" autocomplete="off"></label>
|
|
58
57
|
<label class="dbp-field dbp-switch"><input type="checkbox" class="form-switch" name="locked"${d.site.locked?" checked":""}>
|
|
59
58
|
<span>Lock the layout <span data-help="Everyone but administrators sees the site's default layout and cannot add, move, resize or remove widgets - they can still fold and refresh them. Set the default with Make this the default for everyone. Turning the lock off gives people their own layouts back."></span></span></label>`:""}
|
|
60
59
|
<div class="dbp-settings-actions">
|
|
61
60
|
<button type="button" class="btn btn-ghost" data-act="cancel">Cancel</button>
|
|
62
61
|
<button type="submit" class="btn btn-primary"><span data-icon="check" data-icon-size="14"></span> Save</button>
|
|
63
|
-
</div>`,t.element.appendChild(a);const s=
|
|
62
|
+
</div>`,t.element.appendChild(a);const s=re(a,{position:"left"});I.scan(a),a.querySelector('[data-act="cancel"]').addEventListener("click",()=>t.close()),a.addEventListener("submit",async n=>{n.preventDefault();const l=a.elements,p={name:l.name.value.trim(),greeting:l.greeting.checked,columns:Number(l.columns.value),density:l.density.value,updates:l.updates.value,home:l.home.checked};try{const u=await m("/dashboard/settings",{method:"PUT",body:JSON.stringify(p)});if(d={...d,mine:u.mine,site:u.site,name:u.name},d.isAdmin&&l.siteName){const R=await m("/dashboard/settings/site",{method:"PUT",body:JSON.stringify({name:l.siteName.value.trim(),locked:l.locked.checked})});d.site=R.site,d.name=u.mine.name||R.site.name}ie(),k(),S();try{document.dispatchEvent(new CustomEvent("dm:sidebar-rebuild"))}catch{}E.toast("Dashboard settings saved.",{type:"success"}),s?.(),t.close()}catch(u){E.toast(`Not saved: ${u.message||u}`,{type:"error"})}}),t.open()}if(await me(),!K())return;d.locked||C(h,{id:"dashboard-add-widget",icon:"plus",title:"Add widget",onClick:x}),C(h,{id:"dashboard-fold-all",icon:"chevrons-up",title:"Fold or open every widget",onClick:()=>Z(z(i))}),C(h,{id:"dashboard-refresh",icon:"refresh-cw",title:"Refresh every widget",onClick:()=>b.querySelectorAll(".dbp-card").forEach(v)}),d.locked||C(h,{id:"dashboard-reset",icon:"rotate-ccw",title:"Reset to the site's default layout",onClick:async()=>{if(await E.confirm("Go back to the site's default layout? All your boards are replaced by the one default board."))try{await m("/dashboard/layout",{method:"DELETE"});const e=await m("/dashboard/boards",{method:"GET"});o={version:2,active:e.active,boards:e.boards},i={version:1,widgets:w().widgets},q=JSON.stringify(o),O=e?.source||"built-in",k(),S(),E.toast("Back to the default layout.",{type:"success"})}catch(e){E.toast(`Could not reset: ${e.message||e}`,{type:"error"})}}}),j&&C(h,{id:"dashboard-make-default",icon:"users",title:"Make this the default for everyone",onClick:async()=>{if(await E.confirm("Make this layout the default? Everyone who has not arranged their own Dashboard sees it. (Widgets they may not use are left out for them.)"))try{await m("/dashboard/layout/default",{method:"PUT",body:JSON.stringify(i)}),E.toast("This is now the default layout.",{type:"success"})}catch(e){E.toast(`Could not set the default: ${e.message||e}`,{type:"error"})}}}),C(h,{id:"dashboard-settings",icon:"settings",title:"Dashboard settings",onClick:de});const $e=E.contextMenu(c,{cursor:!1,inherit:!1,density:"compact",items:()=>[{header:w().name},...d.locked?[]:[{label:"Add widget\u2026",icon:"plus",action:()=>x()},{label:"New board\u2026",icon:"layout",disabled:o.boards.length>=12,action:()=>J()},{type:"divider"}],{label:z(i)?"Fold every widget":"Open every widget",icon:z(i)?"chevrons-up":"chevrons-down",action:()=>Z(z(i))},{label:"Refresh every widget",icon:"refresh-cw",action:()=>b.querySelectorAll(".dbp-card").forEach(v)},{type:"divider"},{label:"Dashboard settings\u2026",icon:"settings",action:()=>de()}]});f.add(()=>$e?.destroy?.()),f.add(re(c,{position:"bottom"})),I.scan(c),ie(),k(),S();const Ae=ue(location.hash).isNew&&!d.locked&&o.boards.length<be;M(),Ae&&J()}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import{api as
|
|
1
|
+
import{api as L,apiRequest as m}from"../api.js";import{addBannerAction as ne}from"../lib/plugin-chrome.js";import{helpPopovers as $e}from"../lib/help-popover.js";import{openMenuAt as re,viewLifetime as Ee}from"../lib/tool-kit.js";import{SHAPES as ie,showLoading as j}from"../lib/loading.js";import{BADGE_SOURCES as Se,activityRows as Ae,attentionFrom as Pe,canAny as _,chartSeries as Re,isSitePath as O,kpisFrom as Te}from"../lib/dashboard-arrange.js";const le=/^(#\/?)?(\?.*)?$/,Le=3e4,je="https://cdn.jsdelivr.net/npm/chart.js@4/dist/chart.umd.js",_e={type:"custom",template:'<div class="skeleton skeleton-rect" style="flex:1;aspect-ratio:auto;height:240px"></div>'},Oe=`
|
|
2
2
|
<div class="dbx-row" data-id="{{id}}" data-href="{{href}}" data-url="{{url}}" tabindex="0" data-bind-class="rowCls">
|
|
3
3
|
<span class="dbx-main"><strong>{{text}}</strong><small data-bind-hidden="!meta">{{meta}}</small></span>
|
|
4
4
|
<span class="dbx-value" data-bind-hidden="!value">{{value}}</span>
|
|
5
5
|
<span class="dbx-chip" data-bind-hidden="!chip" data-bind-class="chipCls">{{chip}}</span>
|
|
6
6
|
<button type="button" class="dbx-kebab" aria-label="Actions" title="Actions" data-bind-hidden="!menu">\u22EE</button>
|
|
7
|
-
</div>`;let
|
|
7
|
+
</div>`;let b=null;function Be(){return window.Chart?Promise.resolve(!0):(b||(b=new Promise(i=>{const o=document.createElement("script");o.src=je,o.async=!0,o.onload=()=>i(!0),o.onerror=()=>{b=null,i(!1)},document.head.appendChild(o)})),b)}const c=i=>Number(i||0).toLocaleString("en-GB"),qe=i=>`${Number(i)>=0?"+":""}${Number(i).toFixed(0)}%`,v=(i,o,d,l={})=>({id:`${i}${o}`,text:d,meta:"",href:"",url:d,value:"",chip:"",chipCls:"",rowCls:"",menu:!0,...l}),u=Ee(le);export const dashboardView={templateUrl:"/admin/js/templates/dashboard.html",async onMount(i){u.reset();const o=i.get(0).querySelector(".dbx")||i.get(0),d=()=>le.test(location.hash)&&o.isConnected,l=M.observable(null),g=M.observable({}),y=M.observable(!1),w=M.observable(""),x=M.observable(null),k=M.observable(Date.now()),p=M.observable(null),C=M.observable(!!window.Chart);let f=[],B=new Set;const ce=M.computed(()=>Te(l.value,g.value)),de=M.computed(()=>{const e=x.value;if(!e)return"";const a=Math.floor((k.value-e)/6e4);return a<1?"Updated just now":`Updated ${a} min ago`}),q=M.observable([]),F=M.observable([]),N=M.observable([]),D=M.observable([]),U=M.observable([]),H=M.observable([]),G=M.observable([]),K=M.observable([]);u.add(M.effect(()=>{const e=l.value,a=t=>t==="danger"?"is-danger":"is-warn";q.value=Pe(g.value,e?.health?.checks||[]).map(t=>({id:t.id,text:`${t.title}: ${t.text}`,meta:t.meta,href:t.href,url:"",value:"",chip:t.tone==="danger"?"Urgent":"Check",chipCls:a(t.tone),rowCls:a(t.tone)+(t.href?"":" is-static"),menu:!!t.href})),F.value=Ae(e?.activity||[],k.value).map(t=>({id:t.id,text:t.text,meta:t.meta,href:t.href,url:t.url,value:"",chip:"",chipCls:"",rowCls:"",menu:!0})),N.value=(e?.topPages||[]).map((t,r)=>v("t",r,t.url,{value:c(t.hits),chip:t.deltaPct==null?"":qe(t.deltaPct),chipCls:Number(t.deltaPct)>=0?"is-ok":"is-danger"})),D.value=(e?.spikes||[]).map((t,r)=>v("s",r,t.url,{value:c(t.hits),chip:`\xD7${Number(t.ratio)||0}`,chipCls:"is-warn"}));const s=e?.journeys;U.value=(s?.entry||[]).map((t,r)=>v("e",r,t.url,{value:c(t.count)})),H.value=(s?.exit||[]).map((t,r)=>v("x",r,t.url,{value:c(t.count)})),G.value=(s?.paths||[]).map((t,r)=>({id:`p${r}`,text:`${t.from} \u2192 ${t.to}`,meta:"",href:"",url:"",value:c(t.count),chip:"",chipCls:"",rowCls:"is-static",menu:!1}));const n={ok:["OK","is-ok"],warn:["Look","is-warn"],fail:["Failing","is-danger"]};K.value=(e?.health?.checks||[]).map((t,r)=>({id:`c${r}`,text:String(t.label||t.id),meta:Array.isArray(t.detail)?t.detail.slice(0,5).join(", "):"",href:"",url:"",value:String(t.value??""),chip:(n[t.level]||n.warn)[0],chipCls:(n[t.level]||n.warn)[1],rowCls:"is-static",menu:!1}))}));const ue=M.computed(()=>!!l.value?.journeys?.totalSessions),he=M.computed(()=>{const e=l.value?.journeys;return e?.totalSessions?`Bounce rate ${(Number(e.bounceRate)*100).toFixed(1)}%`:""}),$=M.computed(()=>Re(l.value?.traffic?.series)),S=M.computed(()=>C.value&&!$.value.empty),pe=M.computed(()=>y.value?l.value?C.value?"No page views recorded yet.":"The chart could not load (Chart.js).":"Traffic needs an administrator.":"Loading\u2026"),me=M.computed(()=>!!p.value?.enabled),fe=M.computed(()=>{const e=p.value||{},a=(e.hits||0)+(e.misses||0),s=n=>{if(!n)return"never";const t=Math.max(0,Math.floor((Date.now()-new Date(n).getTime())/1e3));return t<60?`${t}s ago`:t<3600?`${Math.floor(t/60)} min ago`:t<86400?`${Math.floor(t/3600)} h ago`:`${Math.floor(t/86400)} days ago`};return{hitRate:e.hitRate==null?"-":`${(e.hitRate*100).toFixed(1)}%`,hitsMisses:a?`${c(e.hits)} hits \xB7 ${c(e.misses)} misses`:"no visits yet",size:e.maxItems?`${c(e.size)} / ${c(e.maxItems)}`:c(e.size),driver:e.driver?`driver: ${e.driver}`:"",writes:c(e.writes),invalidations:`${c(e.invalidations)} invalidations`,cleared:s(e.lastClearedAt)}});let A=0;async function V(){const e=Se.filter(s=>_(f,s.permission)),a=await Promise.all(e.map(s=>m(s.countUrl,{method:"GET"}).catch(()=>null)));d()&&(g.value=Object.fromEntries(e.map((s,n)=>[s.key,a[n]]).filter(([,s])=>s)))}async function z({first:e=!1}={}){const a=++A,s=e?[j(o.querySelectorAll(".dbx-kpi-value"),ie.figure),j(o.querySelectorAll(".dbx-loading"),{...ie.rows,count:3}),j(o.querySelector(".dbx-chart-note"),_e)]:[],n=()=>Promise.all(s.map(t=>t.done()));try{f=(await m("/auth/permissions",{method:"GET"}).catch(()=>({})))?.permissions||[];const[r,Me,oe]=await Promise.all([L.dashboard.summary().catch(T=>({__error:T})),m("/cache/status",{method:"GET"}).catch(()=>null),_(f,"pages")?L.pages.list().catch(()=>[]):Promise.resolve([]),V()]);if(await n(),a!==A||!d())return;B=new Set((Array.isArray(oe)?oe:[]).map(T=>T.urlPath).filter(Boolean)),r?.__error?(l.value=null,w.value=/403|forbidden/i.test(String(r.__error?.message))?"":`The dashboard could not load: ${r.__error.message}`):(l.value=r,w.value=r?.warnings?.length?`Some parts could not load: ${r.warnings.join(" \xB7 ")}`:""),p.value=Me,x.value=Date.now()}finally{await n(),a===A&&(y.value=!0)}}let be=0;async function ve(){if(!(!d()||!l.peek())){k.value=Date.now();try{const e=await L.dashboard.summaryLite();if(!d())return;const a={...l.peek()};e.traffic&&(a.traffic={...a.traffic,...e.traffic}),e.realtime&&(a.realtime=e.realtime),e.spikes&&(a.spikes=e.spikes),e.health&&(a.health={...a.health,status:e.health.status}),l.value=a,x.value=Date.now()}catch{}++be%2===0&&V(),m("/cache/status",{method:"GET"}).then(e=>{d()&&(p.value=e)}).catch(()=>{})}}const ge=setInterval(ve,Le);u.add(()=>clearInterval(ge));let h=null;const P=o.querySelector(".dbx-canvas"),ye=()=>{const e=getComputedStyle(o),a=(s,n)=>e.getPropertyValue(s).trim()||n;return{line:a("--dm-primary","#5b8cff"),text:a("--dm-text-muted","#94a3b8"),grid:a("--dm-border","rgba(127,127,127,.2)")}},we=e=>/^#[0-9a-f]{6}$/i.test(e)?`${e}26`:"transparent";function J(){if(!S.peek()||!P?.isConnected||!P.offsetParent)return;const{labels:e,values:a}=$.peek(),{line:s,text:n,grid:t}=ye();if(h){h.data.labels=e,h.data.datasets[0].data=a,h.update("none");return}h=new window.Chart(P,{type:"line",data:{labels:e,datasets:[{data:a,borderColor:s,backgroundColor:we(s),fill:!0,tension:.3,pointRadius:0,borderWidth:2}]},options:{responsive:!0,maintainAspectRatio:!1,plugins:{legend:{display:!1}},scales:{x:{ticks:{maxTicksLimit:8,color:n},grid:{color:t}},y:{beginAtZero:!0,ticks:{precision:0,color:n},grid:{color:t}}}}})}u.add(M.effect(()=>{$.value,S.value,requestAnimationFrame(J)})),u.add(()=>{try{h?.destroy()}catch{}h=null});const W=()=>[...document.body.classList].filter(e=>/^dm-(theme|custom)-/.test(e)).sort().join(" ");let Z=W();const Q=new MutationObserver(()=>{const e=W();if(e!==Z){Z=e;try{h?.destroy()}catch{}h=null,requestAnimationFrame(J)}});Q.observe(document.body,{attributes:!0,attributeFilter:["class"]}),u.add(()=>Q.disconnect());const R=e=>new URL(e,location.origin).href,X=()=>_(f,"pages"),Y=e=>O(e)&&B.has(e.split(/[?#]/)[0].replace(/\/$/,"")||"/");async function ee(e,a){try{await navigator.clipboard.writeText(e),E.toast(`${a} copied.`,{type:"success"})}catch{E.toast("Could not copy - select it and copy it by hand.",{type:"error"})}}function te(e){const{href:a,url:s}=e.dataset;if(a){location.hash=a;return}if(s&&X()&&Y(s)){location.hash=`#/pages/edit${s.split(/[?#]/)[0]}`;return}s&&O(s)&&window.open(R(s),"_blank","noopener")}function xe(e){const{href:a,url:s}=e.dataset,n=[{header:(e.querySelector(".dbx-main strong")?.textContent||"").slice(0,60)}];return a&&n.push({label:"Open",icon:"arrow-right",action:()=>{location.hash=a}}),s&&O(s)?(n.push({label:"Open on the site",icon:"external-link",action:()=>window.open(R(s),"_blank","noopener")}),n.push({label:"Copy address",icon:"copy",action:()=>ee(R(s),"Address")}),X()&&n.push({label:"Edit the page",icon:"edit",disabled:!Y(s),action:()=>{location.hash=`#/pages/edit${s.split(/[?#]/)[0]}`}})):s&&n.push({label:"Copy address",icon:"copy",action:()=>ee(s,"Address")}),n.length>1?n:[]}for(const e of[".dbx-attention",".dbx-activity",".dbx-top-pages",".dbx-spikes",".dbx-entry",".dbx-exit",".dbx-paths",".dbx-checks"]){const a=o.querySelector(e);a&&(a.innerHTML=Oe)}const ke=M.applyBindings({summary:l,loaded:y,warning:w,asOf:de,kpi:ce,attention:q,activity:F,topPages:N,spikes:D,entry:U,exit:H,paths:G,checks:K,hasJourneys:ue,bounce:he,hasChart:S,chartNote:pe,cache:p,cacheOn:me,cacheView:fe},o,{methods:{async toggleCache(e){const a=!!e?.target?.checked;try{p.value=await m("/cache/toggle",{method:"POST",body:JSON.stringify({enabled:a})}),E.toast(`The cache is ${a?"on":"off"}.`,{type:"success"})}catch(s){e?.target&&(e.target.checked=!a),E.toast(s.message||"Could not change the cache.",{type:"error"})}},async clearCache(){if(await E.confirm("Clear the whole response cache? Pages render afresh until it fills again."))try{p.value=await m("/cache/clear",{method:"POST"}),E.toast("Cache cleared.",{type:"success"})}catch(e){E.toast(e.message||"Could not clear the cache.",{type:"error"})}}}});u.add(()=>ke.dispose()),u.add($e(o,{position:"bottom"}));const Ce=E.contextMenu(o,{cursor:"glyph",match:".dbx-row",inherit:!1,density:"compact",items:e=>xe(e)});u.add(()=>Ce?.destroy?.());const ae=e=>{const a=e.target.closest(".dbx-row");if(!a||!o.contains(a))return;const s=e.target.closest(".dbx-kebab");if(s){re(s);return}te(a)},se=e=>{const a=e.target.closest(".dbx-row");!a||!o.contains(a)||(e.key==="F10"&&e.shiftKey?(e.preventDefault(),re(a)):e.key==="Enter"&&e.target===a&&te(a))};o.addEventListener("click",ae),o.addEventListener("keydown",se),u.add(()=>{o.removeEventListener("click",ae),o.removeEventListener("keydown",se)}),ne(i,{id:"dashboard-refresh",icon:"refresh-cw",title:"Refresh",onClick:()=>z()}),ne(i,{id:"dashboard-new-page",icon:"plus",title:"New page",onClick:()=>{location.hash="#/pages/new"}}),I.scan(o),Be().then(e=>{d()&&(C.value=e)}),await z({first:!0}),d()&&I.scan(o)}};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{apiRequest as i}from"../api.js";import{viewLifetime as n}from"../lib/tool-kit.js";const o=/^#\/dashboards\/?(\?.*)?$/,a=n(o);export const dashboardsView={templateUrl:"/admin/js/templates/dashboards.html",async onMount(t){a.reset();const e=await i("/dashboard/pro",{method:"GET"}).catch(()=>null);if(!o.test(location.hash))return;const s=t.get(0).querySelector(".dbx")||t.get(0);if(!e?.enabled){s.innerHTML='<p class="dbp-empty">Your own boards come with Dashboard Pro, which is not switched on for this site. Find it in the Marketplace. <a href="#/">Open the Dashboard</a></p>';return}const{mountBoard:r}=await import("./dashboard-board.js");await r(t,a,{isAdmin:e.isAdmin===!0})}};
|
package/admin/js/views/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{dashboardView as o}from"./dashboard.js";import{
|
|
1
|
+
import{dashboardView as o}from"./dashboard.js";import{dashboardsView as i}from"./dashboards.js";import{pagesView as r}from"./pages.js";import{pageEditorView as t}from"./page-editor.js";import{settingsView as e}from"./settings.js";import{themeView as m}from"./theme.js";import{notificationsView as p}from"./notifications.js";import{layoutsView as n}from"./layouts.js";import{mediaView as s}from"./media.js";import{loginView as f}from"./login.js";import{usersView as w}from"./users.js";import{userEditorView as c}from"./user-editor.js";import{pluginsView as V}from"./plugins.js";import{pluginCodeView as a}from"./plugin-code.js";import{documentationView as d}from"./documentation.js";import{tutorialsView as l}from"./tutorials.js";import{docViews as u}from"./doc-pages.js";import{pluginGuidesView as E,pluginGuideView as g}from"./plugin-guides.js";import{apiReferenceView as b}from"./api-reference.js";import{collectionsView as h}from"./collections.js";import{collectionEditorView as v}from"./collection-editor.js";import{collectionEntriesView as y}from"./collection-entries.js";import{formsView as j}from"./forms.js";import{formEditorView as k}from"./form-editor.js";import{formSubmissionsView as L}from"./form-submissions.js";import{viewsListView as x}from"./views-list.js";import{viewEditorView as M}from"./view-editor.js";import{viewPreviewView as D}from"./view-preview.js";import{actionsListView as G}from"./actions-list.js";import{actionEditorView as P}from"./action-editor.js";import{proDocsView as R}from"./pro-docs.js";import{blocksView as S}from"./blocks.js";import{blockEditorView as A}from"./block-editor.js";import{componentsView as C}from"./components.js";import{componentEditorView as N}from"./component-editor.js";import{myProfileView as T}from"./my-profile.js";import{rolesView as U}from"./roles.js";import{roleEditorView as q}from"./role-editor.js";import{effectsView as z}from"./effects.js";import{searchView as B}from"./search.js";import{contentApiView as F}from"./content-api.js";import{notesView as H}from"./notes.js";import{todoView as I}from"./todo.js";import{calculatorView as J}from"../lib/calculator.js";import{analyticsView as K}from"./analytics.js";import{seoView as O}from"./seo.js";import{contactsView as Q}from"./contacts.js";import{callNewView as W}from"./call-new.js";import{menusView as X}from"./menus.js";import{contextMenusView as Y}from"./context-menus.js";import{contextMenuEditorView as Z}from"./context-menu-editor.js";import{menuEditorView as _}from"./menu-editor.js";import{menuLocationsView as $}from"./menu-locations.js";import{projectsView as oo}from"./projects.js";import{projectDetailView as io}from"./project-detail.js";import{projectSettingsView as ro}from"./project-settings.js";import{apiTokensView as to}from"./api-tokens.js";import{apiEndpointsView as eo}from"./api-endpoints.js";import{apiEndpointEditorView as mo}from"./api-endpoint-editor.js";const po={templateUrl:"",async onMount(){location.hash="#/menus"}};export const views={projects:oo,projectDetail:io,projectSettings:ro,apiTokens:to,apiEndpoints:eo,apiEndpointEditor:mo,dashboard:o,dashboards:i,pages:r,pageEditor:t,settings:e,theme:m,layouts:n,media:s,login:f,users:w,userEditor:c,plugins:V,pluginCode:a,documentation:d,tutorials:l,apiReference:b,collections:h,collectionEditor:v,collectionEntries:y,forms:j,formEditor:k,formSubmissions:L,viewsList:x,viewEditor:M,viewPreview:D,actionsList:G,actionEditor:P,proDocs:R,blocks:S,blockEditor:A,components:C,componentEditor:N,myProfile:T,roles:U,roleEditor:q,effects:z,search:B,contentApi:F,notes:H,todo:I,calculator:J,analytics:K,seo:O,contacts:Q,callNew:W,notifications:p,menus:X,contextMenus:Y,contextMenuEditor:Z,menuEditor:_,menuLocations:$,menusRedirect:po,pluginGuides:E,pluginGuide:g,...u};
|