domma-cms 1.15.0 → 1.16.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/admin/css/admin.css +1 -1
- package/admin/js/lib/admin-brand.js +1 -1
- package/admin/js/lib/google-analytics-panel.js +9 -0
- package/admin/js/lib/google-business-callback.js +1 -0
- package/admin/js/lib/google-business-import.js +3 -0
- package/admin/js/lib/google-business-panel.js +32 -0
- package/admin/js/lib/seo-defaults.js +1 -0
- package/admin/js/lib/sidebar-renderer.js +5 -5
- package/admin/js/lib/site-search-section.js +1 -0
- package/admin/js/lib/site-settings-arrange.js +3 -3
- package/admin/js/lib/topbar-account.js +1 -1
- package/admin/js/lib/topbar-cache.js +6 -0
- package/admin/js/lib/topbar-notifications.js +4 -4
- package/admin/js/templates/docs/api-navigation.html +9 -3
- package/admin/js/templates/docs/usage-navigation.html +4 -0
- package/admin/js/templates/docs/usage-site-settings.html +7 -5
- package/admin/js/templates/settings.html +8 -41
- package/admin/js/views/analytics.js +8 -6
- package/admin/js/views/menu-editor.js +16 -16
- package/admin/js/views/search.js +2 -2
- package/admin/js/views/seo.js +50 -42
- package/admin/js/views/settings.js +3 -3
- package/docs/DommaFeatures.md +2 -1
- package/docs/google-business-profile.md +44 -0
- package/docs/screenshots/google-business-profile-review.png +0 -0
- package/docs/screenshots/notifications-cache.png +0 -0
- package/docs/screenshots/site-settings-search.png +0 -0
- package/package.json +1 -1
- package/public/js/site.js +1 -1
- package/server/routes/api/analytics.js +19 -0
- package/server/routes/api/google-business.js +64 -0
- package/server/routes/api/menus.js +8 -4
- package/server/routes/api/seo.js +14 -3
- package/server/services/adminBrand.js +1 -0
- package/server/services/googleBusiness.js +225 -0
- package/server/services/menus.js +27 -2
- package/server/services/renderer.js +2 -0
- package/server/templates/page.html +1 -1
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# Google Business Profile
|
|
2
|
+
|
|
3
|
+
Open SEO and choose its settings cog. Site defaults and the Google Business Profile connection are here. Google Analytics has its own section in the Analytics tool's settings.
|
|
4
|
+
|
|
5
|
+
## Set up Google access
|
|
6
|
+
|
|
7
|
+
Use a Google Cloud project approved for Business Profile API access. Approval is a Google process and must be completed before the connection can retrieve locations. Follow [Google's prerequisites](https://developers.google.com/my-business/content/prereqs).
|
|
8
|
+
|
|
9
|
+
1. Enable the My Business Account Management API and My Business Business Information API in the approved project.
|
|
10
|
+
2. Configure the OAuth consent screen. If the app is in testing, add the Google account that manages the business as a test user.
|
|
11
|
+
3. Create an OAuth client of type Web application.
|
|
12
|
+
4. Copy the exact return address shown under Google app setup in SEO settings into the client's Authorized redirect URIs. It ends with `/api/seo/google-business/callback`.
|
|
13
|
+
5. Enter the client ID and client secret, then choose Connect Google. Sign in with an owner or manager of the Business Profile and grant access.
|
|
14
|
+
|
|
15
|
+
Google requires the `business.manage` scope to read these resources. This version uses the API to read business details and revoke its own connection. See [Google's OAuth guide](https://developers.google.com/my-business/content/implement-oauth) and [basic setup](https://developers.google.com/my-business/content/basic-setup).
|
|
16
|
+
|
|
17
|
+
The return address uses the trusted Site URL. Open the admin at that address when connecting. HTTPS is required, with HTTP allowed for localhost development.
|
|
18
|
+
|
|
19
|
+
## Review and import
|
|
20
|
+
|
|
21
|
+
Choose the business account, then the location. More accounts and More locations load subsequent result pages. Choose Review details to fetch the selected location from Google.
|
|
22
|
+
|
|
23
|
+
Each proposed field appears beside its current value. Empty fields are selected by default. Select any replacements you want, then choose Apply selected details to form. Save SEO settings to publish the resulting structured data on the website.
|
|
24
|
+
|
|
25
|
+
The import can offer the business name, website, phone, public storefront address, Google Maps profile link, LocalBusiness type and structured data switch. Existing logo, email and profile links are retained. Missing Google fields do not clear existing website values. Review address fields before publishing them, especially for a service business.
|
|
26
|
+
|
|
27
|
+
Google listing edits, review replies, posts and automatic synchronisation are outside this version. Imported details are a reviewed copy and can be edited in SEO afterwards.
|
|
28
|
+
|
|
29
|
+
## Reconnect and disconnect
|
|
30
|
+
|
|
31
|
+
Reconnect Google renews authorization. An empty client secret field keeps the saved secret when the client ID is unchanged. Google may ask you to grant access again after an app in testing expires or access is revoked.
|
|
32
|
+
|
|
33
|
+
Disconnect removes this site's saved credentials and attempts to revoke the Google token. Previously imported details remain in SEO. If Google cannot be reached for revocation, the screen explains how to remove the app from your Google account permissions.
|
|
34
|
+
|
|
35
|
+
The encrypted connection and its encryption key live under `content/sessions/google-business/`, which is excluded from site repositories. Keep this private runtime directory in protected backups if you want connections to survive a restore. The key and credential files are readable only by their owner. Tokens and client secrets are never returned by the status API, placed in browser drafts, or stored in `site.json`.
|
|
36
|
+
|
|
37
|
+
## Troubleshooting
|
|
38
|
+
|
|
39
|
+
- Access refused: check Google's API approval, enable both APIs and confirm the Google account manages the business.
|
|
40
|
+
- No locations: select another account or use the Google account that owns or manages the listing.
|
|
41
|
+
- Redirect mismatch: use the exact return address shown in SEO and the Web application OAuth client type.
|
|
42
|
+
- Sign-in expired: choose Connect again in the same browser. Sign-in tickets last ten minutes and can be used once.
|
|
43
|
+
- Access revoked: reconnect to grant Google access again.
|
|
44
|
+
- Request limit: wait before retrying. Google applies project quotas.
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/package.json
CHANGED
package/public/js/site.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{applyMobileMenu as oe,initCarouselPopup as ne}from"/public/js/mobile-ui.mjs?v=20261016-mobile-options";import{installSmoothCarets as se}from"/public/js/smooth-caret.js";import{decodeBase64Json as W}from"/public/js/utf8-base64.mjs";import{makeBadgeEl as J,applyColour as U,applyPill as ae,applyFloat as ie,applyHighlight as V,applyItemHighlight as re,itemHighlightAttrs as ce,isCurrentItem as G,menuAlign as le,applyClasses as j,cssClasses as Y,applyEntrance as X}from"/public/js/menu-decor.mjs?v=20261014-menu-entrance";import{applyMenuLook as de}from"/public/js/menu-cards.mjs?v=20261029-glider-layout";import{initPageMotion as me}from"/public/js/page-motion.mjs?v=20261029-page-motion";import{applyFooterShowcase as ue}from"/public/js/footer-showcase.mjs?v=20261029-footer-showcase";import{initFloatingMenus as pe}from"/public/js/menu-float.js?v=20260829-float-panel";import{enhancePickerFields as K,markInvalidFields as Q,showValidationPopup as Z}from"/public/js/forms.js?v=20261025-forms-files-total";import{buildWizardSteps as fe}from"/public/js/form-steps.mjs";import{mountMotionControl as he}from"/public/js/motion-control.mjs?v=20261029-motion-under-switcher";import{applyFormClasses as ee,applySubmitButtons as ge,applyWizardButtons as ye}from"/public/js/form-submit.mjs";import{captureFileFields as te,destroyDropzones as ve,fileBlueprint as be,restoreFileFields as we,setFileLimits as Ce,splitFiles as Se,submitBody as xe,totalTooLarge as ke,wireFileFields as Le}from"/public/js/form-files.mjs";document.addEventListener("click",u=>{const r=u.target?.closest?.(".dm-banner__dismiss");r&&(u.preventDefault(),r.closest(".dm-banner")?.remove())}),$(()=>{se(document);const u=window.__CMS_NAV__||{},r=window.__CMS_SITE__||{};if(r.autoTheme?.enabled){let s=function(o){const c=(o||"07:00").split(":");return+c[0]*60+(+c[1]||0)},n=function(){const o=new Date,c=o.getHours()*60+o.getMinutes();return c>=s(t.dayStart)&&c<s(t.nightStart)?t.dayTheme:t.nightTheme},e=function(o){const c=t.apply&&t.apply[o]||{theme:o,custom:""};Domma.theme.set(c.theme);for(const f of[...document.body.classList])f.startsWith("dm-custom-")&&document.body.classList.remove(f);c.custom&&document.body.classList.add(c.custom)};const t=r.autoTheme;e(n()),setInterval(()=>e(n()),6e4)}const L=$("#site-navbar");if(L.length&&u.brand){let n=function(m,w=0){return Array.isArray(m)?m.filter(d=>!(d&&(d.type==="spacer"||d.type==="theme-switcher"))).filter(d=>!(d&&d.type==="separator"&&w===0)).map(d=>d&&d.type==="separator"?{divider:!0}:Array.isArray(d?.items)?{...d,items:n(d.items,w+1)}:d):m};const t={...u.brand},s=t.size&&t.size!=="md"?` navbar-brand-${t.size}`:"";if(t.logo||t.icon||t.tagline){let m="";t.logo?m+=`<img src="${t.logo}" class="navbar-brand-logo" alt="${t.text||""}">`:t.icon&&(m+=`<span data-icon="${t.icon}" style="width:1.1em;height:1.1em;margin-right:.35em;vertical-align:middle;"></span>`),t.text&&(m+=`<span class="navbar-brand-text${s}">${t.text}</span>`),t.tagline&&(m+=`<small class="navbar-brand-tagline">${t.tagline}</small>`),t.html=m}else s&&t.text&&(t.html=`<span class="navbar-brand-text${s}">${t.text}</span>`);const e=n(u.items||[]),o=u.position==="floating",c=u.orientation==="vertical";L.get(0).querySelector(":scope > .cms-nojs-links")?.remove(),Domma.elements.navbar("#site-navbar",{brand:t,items:e,variant:u.variant||"dark",position:o?"static":u.position||"sticky",collapsible:!0,appearOnHover:!!u.appearOnHover}),Domma.icons.scan("#site-navbar");const f=L.get(0);oe(f,u.mobile);const a=()=>{if(!c)return;const w=f.querySelector('a[aria-current="page"]')?.closest(".navbar-dropdown");w&&!w.classList.contains("open")&&w.querySelector(":scope > .navbar-dropdown-toggle")?.click()};f.classList.add(`dm-nav-align-${le(u)||"centre"}`),c&&L.get(0).classList.add("dm-nav-vertical",u.side==="right"?"dm-nav-vertical-right":"dm-nav-vertical-left");const h=u.dock||{};c&&!o&&$e(f,u.side==="right"),!c&&h.hideOnScroll&&Ee(f),V(f,u),X(f,u),j(f,u.class),o&&(ie(L.get(0),u),pe(L.get(0).parentNode||document));const l=f.querySelector(".navbar-toggle"),y=f.querySelector(".navbar-collapse");if(l&&y){let m=null;const w=()=>{y.classList.contains("show")&&l.click()},d=()=>{m&&(m.remove(),m=null),document.body.classList.remove("dm-drawer-open")},p=()=>{m||(m=document.createElement("div"),m.className="dm-navbar-backdrop",m.addEventListener("click",w),document.body.appendChild(m),document.body.classList.add("dm-drawer-open"))};l.addEventListener("click",()=>{requestAnimationFrame(()=>{y.classList.contains("show")?(a(),p()):d()})}),y.addEventListener("click",v=>{v.target.closest("a")&&requestAnimationFrame(d)}),document.addEventListener("keydown",v=>{v.key==="Escape"&&y.classList.contains("show")&&w()})}const C=f.querySelector(".navbar-nav");if(C){const m=Array.from(C.children);let w=0;for(const d of u.items||[])if(!(d&&d.type==="separator")){if(d&&d.type==="spacer"){const p=document.createElement("li"),v=["xs","sm","md","lg","xl","flex"].includes(d.size)?d.size:"md";p.className=`dm-menu-spacer dm-menu-spacer--${v}`,p.setAttribute("aria-hidden","true"),C.insertBefore(p,m[w]||null);continue}if(d&&d.type==="theme-switcher"){const p=document.createElement("li");p.className="dm-menu-theme-switcher";const v=document.createElement("span");v.dataset.themeSwitcher="menu",v.dataset.position="inline",p.appendChild(v),C.insertBefore(p,m[w]||null);continue}w++}window.CMSThemeSwitcher?.scan(C)}f.querySelectorAll("[data-path]").forEach(m=>{const w=m.dataset.path.split(".").map(Number),d=w.length>1,p=w.reduce((A,i)=>A?.items?.[i],{items:e});if(!p)return;p.colour&&U(m,p.colour),p.pill&&!d&&ae(m,p.pill),re(m,p),j(m.closest("li"),p.class);const v=J(p.badge);v&&m.appendChild(v),G(p.url,location.pathname)&&(m.classList.add("active"),m.setAttribute("aria-current","page"))}),de(f,u,e),a()}let k=null;const x=$("#site-footer");if(x.length){let o=function(i){return String(i??"").replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""")},c=function(i){return o(i).replace(/'/g,"'")},f=function(i){if(i&&i.type==="separator")return'<li class="footer-sep"><hr></li>';if(i&&i.type==="spacer")return`<li class="dm-menu-spacer dm-menu-spacer--${["xs","sm","md","lg","xl","flex"].includes(i.size)?i.size:"md"}" aria-hidden="true"></li>`;if(i&&i.type==="theme-switcher")return'<li class="dm-menu-theme-switcher"><span data-theme-switcher="menu" data-position="inline"></span></li>';const M=c(i.url||"#"),q=o(i.text||""),g=Array.isArray(i.items)&&i.items.length?`<ul>${i.items.map(f).join("")}</ul>`:"",D=ce(i),N=[D.hover&&` data-menu-item-hover="${c(D.hover)}"`,D.active&&` data-menu-item-active="${c(D.active)}"`].filter(Boolean).join(""),O=D.accent?` style="--dm-menu-highlight:${c(D.accent)}"`:"",R=Y(i.class).join(" ");return`<li${R?` class="${c(R)}"`:""}><a href="${M}"${N}${O}>${q}</a>${g}</li>`},h=function(){if(!r.footer)return"";const i=Object.keys(s).filter(q=>t[q]);if(!i.length)return"";const M=Y(r.footer.socialClass).join(" ");return`<div class="footer-social">${i.map(q=>{const{label:g,svg:D}=s[q];return`<a href="${t[q]}" target="_blank" rel="noopener noreferrer" aria-label="${g}" class="footer-social-link${M?` ${c(M)}`:""}">${D}</a>`}).join("")}</div>`},m=function(i){if(!i||i.type==="separator"||i.type==="spacer")return"";if(i.type==="theme-switcher")return`<li class="cms-footer-col">${f(i).replace(/^<li[^>]*>|<\/li>$/g,"")}</li>`;const M=Array.isArray(i.items)&&i.items.length?`<ul>${i.items.map(f).join("")}</ul>`:"",q=C(i)?`<span class="cms-footer-col-title">${o(i.text||"")}</span>`:`<a class="cms-footer-col-title" href="${c(i.url||"#")}">${o(i.text||"")}</a>`,g=["cms-footer-col",...Y(i.class)].join(" ");return`<li class="${c(g)}">${q}${M}</li>`},p=function(i,M,q=!1){return(i||[]).forEach(g=>{g&&(g.type==="separator"||g.type==="spacer"||g.type==="theme-switcher")||(q&&C(g)||M.push(g),Array.isArray(g.items)&&g.items.length&&p(g.items,M))}),M};const t=r.social||{},s={twitter:{label:"X / Twitter",svg:'<svg viewBox="0 0 24 24" fill="currentColor"><path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-4.714-6.231-5.401 6.231H2.742l7.73-8.835L1.254 2.25H8.08l4.259 5.629L18.244 2.25zm-1.161 17.52h1.833L7.084 4.126H5.117z"/></svg>'},facebook:{label:"Facebook",svg:'<svg viewBox="0 0 24 24" fill="currentColor"><path d="M24 12.073c0-6.627-5.373-12-12-12s-12 5.373-12 12c0 5.99 4.388 10.954 10.125 11.854v-8.385H7.078v-3.47h3.047V9.43c0-3.007 1.792-4.669 4.533-4.669 1.312 0 2.686.235 2.686.235v2.953H15.83c-1.491 0-1.956.925-1.956 1.874v2.25h3.328l-.532 3.47h-2.796v8.385C19.612 23.027 24 18.062 24 12.073z"/></svg>'},instagram:{label:"Instagram",svg:'<svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 2.163c3.204 0 3.584.012 4.85.07 3.252.148 4.771 1.691 4.919 4.919.058 1.265.069 1.645.069 4.849 0 3.205-.012 3.584-.069 4.849-.149 3.225-1.664 4.771-4.919 4.919-1.266.058-1.644.07-4.85.07-3.204 0-3.584-.012-4.849-.07-3.26-.149-4.771-1.699-4.919-4.92-.058-1.265-.07-1.644-.07-4.849 0-3.204.013-3.583.07-4.849.149-3.227 1.664-4.771 4.919-4.919 1.266-.057 1.645-.069 4.849-.069zM12 0C8.741 0 8.333.014 7.053.072 2.695.272.273 2.69.073 7.052.014 8.333 0 8.741 0 12c0 3.259.014 3.668.072 4.948.2 4.358 2.618 6.78 6.98 6.98C8.333 23.986 8.741 24 12 24c3.259 0 3.668-.014 4.948-.072 4.354-.2 6.782-2.618 6.979-6.98.059-1.28.073-1.689.073-4.948 0-3.259-.014-3.667-.072-4.947-.196-4.354-2.617-6.78-6.979-6.98C15.668.014 15.259 0 12 0zm0 5.838a6.162 6.162 0 100 12.324 6.162 6.162 0 000-12.324zM12 16a4 4 0 110-8 4 4 0 010 8zm6.406-11.845a1.44 1.44 0 100 2.881 1.44 1.44 0 000-2.881z"/></svg>'},linkedin:{label:"LinkedIn",svg:'<svg viewBox="0 0 24 24" fill="currentColor"><path d="M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433a2.062 2.062 0 01-2.063-2.065 2.064 2.064 0 112.063 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z"/></svg>'},github:{label:"GitHub",svg:'<svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12"/></svg>'},youtube:{label:"YouTube",svg:'<svg viewBox="0 0 24 24" fill="currentColor"><path d="M23.495 6.205a3.007 3.007 0 00-2.088-2.088c-1.87-.501-9.396-.501-9.396-.501s-7.507-.01-9.396.501A3.007 3.007 0 00.527 6.205a31.247 31.247 0 00-.522 5.805 31.247 31.247 0 00.522 5.783 3.007 3.007 0 002.088 2.088c1.868.502 9.396.502 9.396.502s7.506 0 9.396-.502a3.007 3.007 0 002.088-2.088 31.247 31.247 0 00.5-5.783 31.247 31.247 0 00-.5-5.805zM9.609 15.601V8.408l6.264 3.602z"/></svg>'}};let n='<div class="footer-inner container">';const e=window.__CMS_FOOTER__||{primary:[],legal:[],copyright:""},a=(()=>{const i=r.cookieConsent;if(!i||i.showSettingsLink===!1)return"";const M=q=>(q||[]).some(g=>g&&(g.url==="#cookie-settings"||M(g.items)));return M(e.primary)||M(e.legal)?"":`<li class="cms-cookie-settings"><a href="#cookie-settings">${o(i.settingsLinkText||"Cookie settings")}</a></li>`})(),l=["columns","row"].includes(r.footer?.layout)?r.footer.layout:"auto",y=r.footer?.look==="showcase"||l==="columns"||l==="auto"&&e.primary.some(i=>Array.isArray(i?.items)&&i.items.length),C=i=>y&&!i.url&&Array.isArray(i.items)&&i.items.length>0;if(y){const i=u.brand||{},M=i.text||r.title||"",q=i.logo?`<img src="${c(i.logo)}" class="cms-footer-brand-logo" alt="">`:i.icon?'<span class="cms-footer-brand-icon"></span>':"",g=String(r.footer?.blurb||"").trim();n='<div class="footer-inner footer-inner--columns container"><div class="cms-footer-top">',n+=`<div class="cms-footer-brand">${M||q?`<a class="cms-footer-brand-name" href="${c(i.url||"/")}">${q}<span>${o(M)}</span></a>`:""}${g?`<p class="cms-footer-blurb">${o(g)}</p>`:""}${h()}</div>`,e.primary.length&&(n+=`<nav class="footer-nav footer-nav--primary footer-nav--columns" aria-label="Footer"><ul>${e.primary.map(m).join("")}</ul></nav>`),n+='</div><div class="cms-footer-bottom">',e.copyright&&(n+=`<p class="cms-footer-copyright">${o(e.copyright)}</p>`),(e.legal.length||a)&&(n+=`<nav class="footer-nav footer-nav--legal" aria-label="Legal"><ul>${e.legal.map(f).join("")}${a}</ul></nav>`),n+="</div>"}else e.copyright&&(n+=`<p>${o(e.copyright)}</p>`),e.primary.length&&(n+=`<nav class="footer-nav footer-nav--primary"><ul>${e.primary.map(f).join("")}</ul></nav>`),(e.legal.length||a)&&(n+=`<nav class="footer-nav footer-nav--legal"><ul>${e.legal.map(f).join("")}${a}</ul></nav>`),n+=h();n+="</div>",x.get(0).querySelector(":scope > .cms-nojs-links")?.remove();const w=x.get(0).querySelector(":scope > .cms-footer-blocks");w?.remove(),x.html(n),w&&x.get(0).prepend(w),window.CMSThemeSwitcher?.scan(x.get(0));const d=x.get(0).querySelector(".cms-footer-brand-icon");d&&u.brand?.icon&&d.setAttribute("data-icon",u.brand.icon),j(x.get(0),r.footer?.class),y&&ue(x.get(0),r);const v=[...p(e.primary,[],!0),...p(e.legal,[])];x.get(0).querySelectorAll(".footer-nav a").forEach((i,M)=>{const q=v[M];if(!q)return;q.colour&&U(i,q.colour);const g=J(q.badge);g&&i.appendChild(g),G(q.url,location.pathname)&&(i.classList.add("is-active"),i.setAttribute("aria-current","page"))}),V(x.get(0).querySelector(".footer-nav--primary"),{highlight:e.primaryHighlight}),V(x.get(0).querySelector(".footer-nav--legal"),{highlight:e.legalHighlight}),X(x.get(0).querySelector(".footer-nav--primary"),{entrance:e.primaryEntrance}),X(x.get(0).querySelector(".footer-nav--legal"),{entrance:e.legalEntrance}),j(x.get(0).querySelector(".footer-nav--primary"),e.primaryClass),j(x.get(0).querySelector(".footer-nav--legal"),e.legalClass),k=x.get(0).querySelector(".cms-footer-bottom")||x.get(0).querySelector(".footer-inner")}he(r.motion||{enabled:r.footer?.showMotionToggle!==!1,default:r.footer?.motionDefault,placement:"footer",control:r.footer?.motionControl,label:"Reduce motion"},{footer:k,navbar:document.querySelector("#site-navbar .navbar-container")?.closest("#site-navbar")||null});const I=document.getElementById("site-sidebar");I&&Ae(I),Domma.icons.scan(),Domma.syntax?.scan&&Domma.syntax.scan();const b=$(".page-body");if(b.length){b.find(".accordion").each(function(){Domma.elements.accordion(this,{allowMultiple:this.dataset.multi==="true"})}),b.find(".tabs").each(function(){Domma.elements.tabs(this)}),b.find(".carousel").each(function(){const s={primary:"--dm-primary",secondary:"--dm-secondary",accent:"--dm-primary",text:"--dm-text",surface:"--dm-surface",background:"--dm-background",success:"--dm-success",warning:"--dm-warning",danger:"--dm-danger",info:"--dm-info"},n=h=>{const l=String(h||"").trim();return l?s[l.toLowerCase()]?`var(${s[l.toLowerCase()]})`:CSS.supports("color",l)?l:"":""},e=n(this.dataset.background),o=n(this.dataset.arrowBackground),c=n(this.dataset.arrowColor);e&&(this.style.setProperty("--dm-carousel-background",e),this.classList.add("dm-carousel-custom-background")),o&&this.style.setProperty("--dm-carousel-arrow-background",o),c&&this.style.setProperty("--dm-carousel-arrow-color",c);const f=["top","middle","bottom"].includes(this.dataset.arrowPosition)?this.dataset.arrowPosition:"middle";this.classList.add(`dm-carousel-arrows-${f}`);const a={autoplay:this.dataset.autoplay==="true",interval:parseInt(this.dataset.interval,10)||5e3,loop:this.dataset.loop!=="false",animation:this.dataset.animation||"slide",heightMode:this.dataset.heightMode==="average"?"average":"crop"};if(this.dataset.height){const h=Number(this.dataset.height);Number.isFinite(h)&&h>0&&(a.height=h)}if(this.dataset.animationDuration){const h=parseInt(this.dataset.animationDuration,10);Number.isFinite(h)&&(a.animationDuration=h)}this.dataset.animationEasing&&(a.animationEasing=this.dataset.animationEasing),this.dataset.ctx&&(this._dmCtxSlides=Array.from(this.querySelectorAll(".carousel-slide")).map(h=>h.cloneNode(!0)),this._dmCarouselOpts=a),this._dmCarousel=Domma.elements.carousel(this,a),ne(this)}),b.find(".dm-countdown").each(function(){const s={autoStart:!0},n=String(this.dataset.format||"").trim(),e=String(this.dataset.to||"").trim(),o=/^(\d{4})-(\d{2})-(\d{2})$/.exec(e),c=o?new Date(+o[1],+o[2]-1,+o[3]).getTime():e?new Date(e).getTime():NaN;Number.isFinite(c)?s.duration=Math.max(0,c-Date.now()):this.dataset.duration&&(s.duration=(parseInt(this.dataset.duration,10)||0)*1e3);const f=/^dd:/i.test(n);s.format=f?"hh:mm:ss":n||(s.duration>=36e5?"hh:mm:ss":"mm:ss");const a=this,h=y=>String(y).padStart(2,"0"),l=y=>{const C=Math.max(0,Math.floor(y/1e3)),m=a.querySelector(".dm-timer-time");m&&(m.textContent=`${h(Math.floor(C/86400))}:${h(Math.floor(C%86400/3600))}:${h(Math.floor(C%3600/60))}:${h(C%60)}`)};f&&(s.onTick=l),Domma.elements.timer(this,s),f&&l(s.duration||0)}),b.find("[data-tooltip]:not(.dm-range-slider)").each(function(){Domma.elements.tooltip(this,{content:$(this).data("tooltip"),position:$(this).data("tooltip-position")||"top"})}),b.find(".dm-progression").each(function(){const s=Array.from(this.querySelectorAll(".dm-progression-item")).map(o=>{const c={title:o.querySelector(".dm-progression-item-title")?.textContent?.trim()||"",description:o.querySelector(".dm-progression-item-body")?.innerHTML?.trim()||""};return o.dataset.date&&(c.date=o.dataset.date),o.dataset.status&&(c.status=o.dataset.status),o.dataset.eventIcon&&(c.icon=o.dataset.eventIcon),o.dataset.entryId&&(c._entryId=o.dataset.entryId),c}),n=this.dataset.layout==="centred"?"centered":this.dataset.layout||"vertical",e=Domma.elements.progression(this,{layout:n,theme:this.dataset.theme||"minimal",mode:this.dataset.mode||"timeline",statusIcons:!0,allowHtml:!0,...s.length?{items:s}:{}});if((this.dataset.mode||"timeline")==="timeline"){const o={completed:{label:"Completed",icon:"check-circle",tone:"success"},planned:{label:"Planned",icon:"circle",tone:"secondary"},"in-progress":{label:"In progress",icon:"clock",tone:"primary"},blocked:{label:"Blocked",icon:"alert-circle",tone:"danger"}};this.querySelectorAll(".dm-progression-item").forEach((c,f)=>{const a=s[f];if(!a)return;const h=c.querySelector(".dm-progression-title");if(a.icon&&h){const y=document.createElement("span");y.dataset.icon=a.icon,y.style.cssText="display:inline-flex;vertical-align:middle;margin-right:.4em",y.setAttribute("aria-hidden","true"),h.prepend(y)}const l=o[a.status];if(l&&h){const y=document.createElement("span");y.className=`badge badge-${l.tone} cms-timeline-status`,y.style.cssText="display:inline-flex;align-items:center;gap:.35em;margin:.4rem 0";const C=document.createElement("span");C.dataset.icon=l.icon,C.dataset.iconSize="14",C.setAttribute("aria-hidden","true"),y.append(C,document.createTextNode(l.label)),h.after(y)}})}Domma.icons.scan(this),s.length&&(this._dmCtx={instance:e,items:s})});try{Domma.effects.reveal(".page-body .hero",{animation:"slide-up",duration:480,threshold:.06,stagger:60,once:!1})}catch{}const t=matchMedia("(prefers-reduced-motion: reduce)").matches||document.documentElement.classList.contains("dm-reduced-motion");document.querySelectorAll(".page-body .row[data-reveal]").forEach(s=>{const n=s.dataset.revealMode||"stagger",e=s.dataset.revealAnimation||"slide-up",o=parseInt(s.dataset.revealDuration,10)||(e.startsWith("swoosh")?700:400),c=parseInt(s.dataset.revealStagger,10)||(e.startsWith("swoosh")?90:60),f=parseInt(s.dataset.revealDelay,10)||0,a=s.dataset.revealDirection==="rtl",h=s.dataset.revealReplay==="true",l=Array.from(s.children);if(t&&e.startsWith("swoosh"))return;const y={"slide-up":"translateY(30px)","slide-down":"translateY(-30px)","slide-left":"translateX(30px)","slide-right":"translateX(-30px)",zoom:"scale(0.85)",flip:"perspective(600px) rotateX(15deg)",swoosh:"translateY(36px) scale(0.96)"},C=p=>e!=="swoosh-sides"?y[e]||"":l.length>1&&p===0?"translateX(-64px) rotate(-1.5deg)":l.length>1&&p===l.length-1?"translateX(64px) rotate(1.5deg)":"translateY(40px) scale(0.94)",m=e.startsWith("swoosh"),w=m?"cubic-bezier(.2,.8,.2,1)":"ease",d=(p,v)=>{p.style.opacity="0",p.style.transform=C(v),m&&(p.style.filter="blur(10px)")};l.forEach((p,v)=>{d(p,v),p.style.transition=`opacity ${o}ms ${w}, transform ${o}ms ${w}`+(m?`, filter ${o}ms ${w}`:"");const A=a?l.length-1-v:v;p.style.transitionDelay=n==="stagger"?`${f+A*c}ms`:`${f}ms`}),requestAnimationFrame(()=>requestAnimationFrame(()=>{const p=new IntersectionObserver(v=>{v.forEach(A=>{if(A.isIntersecting){if(A.target.offsetWidth,A.target.style.opacity="1",A.target.style.transform="",m&&(A.target.style.filter=""),!h){p.unobserve(A.target);const i=A.target;i.addEventListener("transitionend",function M(q){q.target!==i||q.propertyName!=="opacity"||(i.removeEventListener("transitionend",M),i.style.transition="",i.style.transitionDelay="")})}}else h&&A.boundingClientRect.top>0&&d(A.target,l.indexOf(A.target))})},{threshold:.1});l.forEach(v=>p.observe(v))}))}),me(document.querySelector(".page-body")||document),b.find(".card[data-collapsible]").each(function(){const s=this.querySelector(".card-header");s&&s.addEventListener("click",()=>this.classList.toggle("is-collapsed"))}),b.find(".dm-so-trigger").each(function(){this.addEventListener("click",()=>{const s=this.dataset.soTarget,n=document.getElementById(s);if(!n)return;const e=E.slideover({title:n.dataset.soTitle||"",size:n.dataset.soSize||"md",position:n.dataset.soPosition||"right"});n.style.display="",e.setContent(n),e.open()})})}if(typeof $.setup=="function"){const t=Object.assign({},window.__CMS_DCONFIG__||{});if(document.querySelectorAll(".dm-page-config[data-config]").forEach(s=>{try{const n=W(s.dataset.config);Object.assign(t,n)}catch{}}),Object.keys(t).length>0){const s={};for(const[n,e]of Object.entries(t)){const o=e?.events?.click,{confirm:c,toast:f,alert:a,prompt:h,...l}=o||{};if(c||f||a||h){$(n).on("click",async function(R){if(R.preventDefault(),c&&!await E.confirm(c))return;let z=null;if(!(h&&(z=await E.prompt(h,{inputPlaceholder:l.promptPlaceholder||"",inputValue:l.promptDefault||""}),z===null))){{const B=$(l.target||this);l.toggleClass&&B.toggleClass(l.toggleClass),l.addClass&&B.addClass(l.addClass),l.removeClass&&B.removeClass(l.removeClass),z!==null&&(l.setText&&B.text(z),l.setVal&&B.val(z),l.setAttr&&B.attr(l.setAttr,z))}l.href&&(window.location.href=l.href),f&&E.toast(f,{type:l.toastType||"success"}),a&&E.alert(a)}});const{target:C,toggleClass:m,addClass:w,removeClass:d,setText:p,setVal:v,setAttr:A,href:i,toastType:M,promptPlaceholder:q,promptDefault:g,...D}=l,N={...e.events};Object.keys(D).length?N.click=C?{target:C,...D}:D:delete N.click;const O={...e,events:N};Object.keys(N).length||delete O.events,Object.keys(O).length&&(s[n]=O)}else s[n]=e}$.setup(s)}}b.length&&P(b.get(0))}),window.wireCTAButtons=P;function P(u){u.querySelectorAll(".dm-cta-trigger").forEach(r=>{r.__ctaWired||(r.__ctaWired=!0,r.addEventListener("click",async()=>{const L=r.dataset.action,k=r.dataset.entry,x=r.dataset.confirm;let I=S.get("auth_token");if(!I){E.toast("Please log in to perform this action.",{type:"warning"});return}if(x&&!await E.confirm(x))return;const b=Array.from(r.childNodes).map(s=>s.cloneNode(!0));r.disabled=!0,r.textContent="Running\u2026";const t=s=>fetch(`/api/actions/${L}/public`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${s}`},body:JSON.stringify(r.dataset.scope?{entryId:k,scope:r.dataset.scope}:{entryId:k})});try{let s=await t(I);if(s.status===401){const e=S.get("auth_refresh_token");if(e){const o=await fetch("/api/auth/refresh",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({refreshToken:e})});if(o.ok){const{token:c}=await o.json();S.set("auth_token",c),I=c,s=await t(I)}}}const n=await s.json().catch(()=>({}));if(!s.ok||n.success===!1)throw new Error(n.error||n.message||`Error ${s.status}`);E.toast(n.message||"Action completed.",{type:"success"})}catch(s){E.toast(s.message||"Action failed.",{type:"error"})}finally{r.disabled=!1,r.textContent="",b.forEach(s=>r.appendChild(s)),Domma.icons.scan(r)}}))})}(function(){const r=document.querySelectorAll('.dm-collection-hydrate[data-collection-scope="mine"]');if(!r.length)return;const L=S.get("auth_token");function k(I,b){const t=document.createElement("div");t.className="dm-collection-display dm-collection-empty";const s=document.createElement("p");if(b){s.appendChild(document.createTextNode("Please "));const n=document.createElement("a");n.href="/login",n.textContent="sign in",s.appendChild(n),s.appendChild(document.createTextNode(" to view your entries."))}else s.textContent=I;return t.appendChild(s),t}function x(I,b){const t=document.createElement("template");t.innerHTML=b||"",I.replaceWith(t.content)}r.forEach(async I=>{const b=I.dataset.collectionAttrs;if(!b)return;if(!L){I.replaceChildren(k("",!0));return}const t=s=>fetch("/api/collections/render-scope",{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${s}`},body:JSON.stringify({attrs:b})});try{let s=L,n=await t(s);if(n.status===401){const o=S.get("auth_refresh_token");if(o){const c=await fetch("/api/auth/refresh",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({refreshToken:o})});if(c.ok){const{token:f}=await c.json();S.set("auth_token",f),s=f,n=await t(s)}}}if(!n.ok){const{error:o}=await n.json().catch(()=>({}));I.replaceChildren(k(o||"Unable to load entries.",!1));return}const{html:e}=await n.json();x(I,e),window.DommaCollectionBrowser?.hydrate&&window.DommaCollectionBrowser.hydrate(document)}catch{I.replaceChildren(k("Unable to load entries.",!1))}}),setTimeout(()=>{typeof Domma<"u"&&Domma.icons?.scan&&Domma.icons.scan(),typeof Domma<"u"&&Domma.syntax?.scan&&Domma.syntax.scan(),P(document.body)},100)})(),(function(){const r=document.querySelectorAll("[data-collection-table]");!r.length||typeof T>"u"||!T.create||r.forEach(L=>{let k;try{k=W(L.dataset.payload)}catch{return}const{columns:x,rows:I,search:b,sortable:t,exportable:s,pageSize:n,empty:e,ctaConfig:o,slug:c,exportCfg:f}=k;if(!x?.length)return;if(o){const l=x.findIndex(y=>y.key==="_cta");l!==-1&&(x[l]={key:"_cta",title:"",render:(y,C)=>{const m=document.createElement("button");if(m.className=`btn btn-${o.style||"primary"} dm-cta-trigger`,m.dataset.action=o.action||"",o.scope&&(m.dataset.scope=o.scope),m.dataset.entry=C._entryId||"",o.confirm&&(m.dataset.confirm=o.confirm),o.icon){const w=document.createElement("span");w.dataset.icon=o.icon,m.appendChild(w),m.appendChild(document.createTextNode(" "))}return m.appendChild(document.createTextNode(o.label||"Run")),m}})}const a="col-table-"+Math.random().toString(36).slice(2,7),h=document.createElement("div");h.id=a,L.replaceChildren(h),T.create("#"+a,{data:I,columns:x,search:b,sortable:t,exportable:s,pageSize:n,emptyMessage:e}),o&&P(h),L._dmTable={columns:x,rows:I,slug:c,exportCfg:f,render(l){T.create("#"+a,{data:l,columns:x,search:b,sortable:t,exportable:s,pageSize:n,emptyMessage:e}),o&&P(h)}}})})(),(function(){const r=document.querySelectorAll("[data-form-inline]");if(!r.length||typeof F>"u")return;function L(t){const s=t&&t.logic;return!!(s&&(s.visibility||s.requirement))}function k(t,s){const n={};return(t||[]).forEach(e=>{if(e.type==="page-break"||e.type==="spacer"||!e.name)return;const o=e.type==="chooser"||!!(e.variant&&["select","radio","checkbox-group"].includes(e.type)),c=e.type==="checkbox"?"boolean":e.type==="date"||e.type==="icon"||e.type==="image"?"string":o?"chooser":e.type,f={...e.formConfig||{}};f.span==="full"&&s&&(f.span=s);const a={type:c,required:L(e)?!1:e.required,label:e.label,options:e.options,formConfig:{...e.placeholder&&{placeholder:e.placeholder},...e.helper&&{hint:e.helper},...e.tooltip&&{tooltip:e.tooltip},...f}};e.minLength!==void 0&&(a.minLength=e.minLength),e.maxLength!==void 0&&(a.maxLength=e.maxLength),e.min!==void 0&&(a.min=e.min),e.max!==void 0&&(a.max=e.max),o&&(e.variant&&(a.variant=e.variant),(e.multiple||e.type==="checkbox-group")&&(a.multiple=!0),e.density&&(a.density=e.density),e.columns&&(a.columns=e.columns),e.accent&&(a.accent=e.accent),e.accentStyle&&(a.accentStyle=e.accentStyle),e.glow&&(a.glow=!0),e.glowColour&&(a.glowColour=e.glowColour),e.shadow&&(a.shadow=e.shadow),e.shadowColour&&(a.shadowColour=e.shadowColour)),n[e.name]=e.type==="file"?be(e,a):a}),n}function x(t){const s={};return(t||[]).forEach(n=>{if(!(!n.name||n.type==="page-break"||n.type==="spacer")&&(n.type==="select"||n.type==="multiselect")&&n.required){const e=(n.options||[])[0];e&&(s[n.name]=typeof e=="object"?e.value:e)}}),s}function I(t,s){(s||[]).forEach(n=>{if(n.type!=="date"||!n.name)return;const e=t.querySelector(`[name="${n.name}"]`);e&&e.type!=="date"&&(e.type="date")})}function b(t,s,n){let e=t.querySelector(".cms-form-message");e||(e=document.createElement("p"),e.className="cms-form-message",t.appendChild(e)),e.textContent=s,e.style.cssText=n?"color:var(--dm-danger);margin-top:.75rem;":"color:var(--dm-success);margin-top:.75rem;"}r.forEach(t=>{let s;try{s=W(t.dataset.formInline)}catch{return}t.querySelector(":scope > .cms-form-embed-summary")?.remove();const n=s.fields||[],e=s.settings||{},o=Number(e.columns)===3?3:2,c=e.layout||"grid",f=n.some(w=>w.type==="page-break"),a=new Map;Ce(t,s.fileLimits,a),Le(t,n);const h=async w=>{if(t.dataset.triggerBlocked==="true"){const d=t._formLogicRuntime,p=d&&d.submitBlock?d.submitBlock().reasons:[];b(t,p.join(" ")||"This form cannot be submitted yet.",!0);const v=new Error("Blocked by a form trigger");throw v.formSubmitFailed=!0,v}try{const d=t.querySelector(".fb-form-honeypot"),p=d?.querySelector('input[type="text"]')||null,v=d?.querySelector('[name="_t"]')||null,A=Object.assign({},w);delete A._t,d?.dataset.hpName&&delete A[d.dataset.hpName],p!==null&&(A._hp=p.value),v!==null&&(A._t=v.value);const i=new Map(a);te(t,i);const M=Se(A,n,i),q=ke(t,M.files);if(q){b(t,q,!0);const D=new Error(q);throw D.formSubmitFailed=!0,D}const g=await H.post(`/api/forms/submit/${s.slug}`,M.files.size?xe(M.values,M.files).body:M.values);if(g&&g.error){b(t,g.error,!0);const D=new Error(g.error);throw D.formSubmitFailed=!0,D}if(g?.redirect){window.location.href=g.redirect;return}for(ve(t);t.firstChild;)t.removeChild(t.firstChild);b(t,g?.message||e.successMessage||"Thank you for your submission.",!1)}catch(d){throw d.formSubmitFailed||b(t,d.message||"Submission failed. Please try again.",!0),d}},l=Date.now();function y(w){const d=w.querySelector("form");if(!d||d.querySelector(".fb-form-honeypot"))return;const p=document.createElement("div");p.className="fb-form-honeypot",p.setAttribute("aria-hidden","true");const v=document.createElement("input");v.name=d.querySelector('[name="website"]')?"_website":"website",p.dataset.hpName=v.name,v.type="text",v.tabIndex=-1,v.autocomplete="url",v.placeholder="https://",p.appendChild(v);const A=document.createElement("input");A.name="_t",A.type="hidden",A.value=l,p.appendChild(A),d.appendChild(p)}function C(w){if(!window.FormLogicEngine||!n.some(p=>p.logic||Array.isArray(p.triggers)&&p.triggers.length))return null;const d=new window.FormLogicEngine.FormLogicRuntime(s,t,w?{baseValues:w}:void 0);if(d.init(),t._formLogicRuntime=d,t._formLogicObserved)return d;if(t._formLogicObserved=!0,t.parentNode&&typeof MutationObserver<"u"){const p=new MutationObserver(function(v){for(const A of v)for(const i of A.removedNodes)if(i===t||i.nodeType===1&&i.contains&&i.contains(t)){t._formLogicRuntime&&t._formLogicRuntime.destroy(),p.disconnect();return}});p.observe(t.parentNode,{childList:!0,subtree:!1}),t._formLogicObserver=p}return d}let m=!1;if(t.addEventListener("dm-form-end",w=>{const d=w.detail?.validation;if(d&&!d.valid){Q(t,d.missing),Z(d);return}m||(m=!0,h(w.detail?.values||{}).catch(()=>{}).finally(()=>{m=!1}))}),f&&F.wizard){const w=fe(n,s.title),d=w.map(g=>({title:g.title,description:g.description,fields:k(g.fields,o)})),p=w.map(g=>g.fields);let v=0,A=null,i=null;const M=g=>{const D=window.FormLogicEngine;if(!D||!D.validateFields)return;const N=D.validateFields(t,p[v]||[]);N.valid||(g.preventDefault(),g.stopImmediatePropagation(),Q(t,N.missing),Z(N))},q=()=>{const g=p[v]||n;ye(t,e),ee(t,e,g),I(t,g),K(t,g),we(t,a),e.honeypot!==!1&&y(t),A&&A.destroy(),A=C(i?.getStepData?i.getStepData():void 0),t.querySelectorAll(".wizard-next, .wizard-submit").forEach(D=>D.addEventListener("click",M,!0))};i=F.wizard(t,{schema:{steps:d},layout:c,columns:o,finishText:e.submitText||"Finish",onSubmit:h,onStepChange:g=>{te(t,a),v=g,setTimeout(q,0)}}),t.addEventListener("dm-form-trigger",g=>{if(g.detail?.type!=="goto-step"||!i?.goToStep)return;const D=Number(g.detail.action.step);!Number.isFinite(D)||D<0||D>=d.length||D!==v&&i.goToStep(D)}),Promise.resolve(i).then(q)}else if(F.render){const w=F.render(t,k(n,o),x(n),{submitText:e.submitText||"Submit",layout:c,columns:o,onSubmit:h});Promise.resolve(w).then(function(){ge(t,e,c),ee(t,e,n),I(t,n),K(t,n),e.honeypot!==!1&&y(t),C()})}})})();function $e(u,r){const L=document.body,k=L.classList,x=new Set((L.dataset.railSite||"").split(" ").filter(Boolean)),I=()=>window.matchMedia("(min-width: 993px)").matches;u.querySelectorAll(".navbar-link, .navbar-dropdown-toggle").forEach(o=>{const c=o.textContent.trim();c&&(o.setAttribute("title",c),o.querySelector("svg, [data-icon]")||(o.dataset.initial=c.charAt(0).toUpperCase()))});const b=document.createElement("button");b.type="button",b.className="dm-nav-collapse-toggle";const t=()=>{const o=k.contains("dm-vnav-collapsed");b.setAttribute("aria-pressed",String(o)),b.setAttribute("aria-label",o?"Expand menu":"Collapse menu"),b.title=b.getAttribute("aria-label");const c=r?"chevrons-left":"chevrons-right",f=r?"chevrons-right":"chevrons-left";b.innerHTML=`<span data-icon="${o?c:f}"></span>`,window.Domma&&Domma.icons&&Domma.icons.scan(b)},s=o=>{k.toggle("dm-vnav-collapsed",o),S.set("nav_collapsed",o),t()};b.addEventListener("click",()=>s(!k.contains("dm-vnav-collapsed"))),t();const n=u.querySelector(".navbar-container")||u;n.insertBefore(b,n.firstChild?.nextSibling||null);const e=o=>{k.toggle("dm-vnav-autohide",o),k.toggle("dm-vnav-collapsible",!o&&x.has("collapsible")),o?k.remove("dm-vnav-collapsed"):x.has("collapsible")&&S.get("nav_collapsed")===!0&&k.add("dm-vnav-collapsed"),u.classList.remove("dm-nav-revealed"),t()};u.addEventListener("pointerdown",o=>{k.contains("dm-vnav-autohide")&&o.pointerType!=="mouse"&&!u.classList.contains("dm-nav-revealed")&&(u.classList.add("dm-nav-revealed"),o.preventDefault())}),document.addEventListener("pointerdown",o=>{u.contains(o.target)||u.classList.remove("dm-nav-revealed")}),window.E&&typeof E.contextMenu=="function"&&E.contextMenu(u,{items:()=>{const o=k.contains("dm-vnav-autohide"),c=k.contains("dm-vnav-collapsed"),f=S.has("nav_autohide")||S.has("nav_collapsed");return[{header:"Menu"},{label:c?"Expand menu":"Collapse to icons",icon:c?"maximize":"minimize",disabled:!I()||o||!x.has("collapsible"),action:()=>s(!c)},{label:o?"Keep on screen":"Hide when not in use",icon:o?"eye":"eye-off",disabled:!I(),action:()=>{S.set("nav_autohide",!o),e(!o)}},{type:"divider"},{label:"Reset to the site's setting",icon:"refresh",disabled:!f,action:()=>{S.remove("nav_autohide"),S.remove("nav_collapsed"),e(x.has("autohide")),k.toggle("dm-vnav-collapsed",!x.has("autohide")&&x.has("start-collapsed")),t()}}]}})}function Ee(u){u.classList.add("dm-nav-scroll-hide");let r=window.scrollY,L=!1;window.addEventListener("scroll",()=>{L||(L=!0,requestAnimationFrame(()=>{L=!1;const k=window.scrollY,x=u.matches(":focus-within")||u.querySelector(".navbar-dropdown.open, .navbar-collapse.show");k>r&&k>u.offsetHeight&&!x?u.classList.add("dm-nav-scrolled-away"):k<r&&u.classList.remove("dm-nav-scrolled-away"),r=k}))},{passive:!0})}function Ae(u){let r={};try{r=JSON.parse(u.dataset.contents||"{}")}catch{}const L=u.closest(".site-main"),k=document.querySelector(".site-content"),x=new Set(String(r.exclude||"").split(",").map(a=>a.trim().toLowerCase()).filter(Boolean)),I=a=>!a.closest(".hero, .card, .accordion, .tabs, figure, #site-sidebar"),b=a=>(k?[...k.querySelectorAll(a)]:[]).filter(I).filter(h=>!x.has(h.textContent.trim().toLowerCase())),t=r.headings==="h2"?b("h2"):r.headings==="h3"?b("h3"):r.headings==="h2h3"?b("h2, h3"):b("h2").length?b("h2"):b("h3");if(!t.length){L?.classList.add("no-sidebar-items");return}const s=new Set([...document.querySelectorAll("[id]")].map(a=>a.id)),n=document.createElement("nav");n.className="dm-contents",n.setAttribute("aria-label",r.title||"Page contents"),r.hover&&(n.dataset.menuHover=r.hover),r.selected&&(n.dataset.menuActive=r.selected),r.accent&&n.style.setProperty("--dm-menu-highlight",r.accent.startsWith("#")?r.accent:`var(--dm-${r.accent})`),r.font&&(n.style.fontFamily=r.font),r.fontSize&&(n.style.fontSize=r.fontSize),r.fontWeight&&(n.style.fontWeight=r.fontWeight);const e=document.createElement("div");e.className="dm-contents-head";const o=document.createElement("p");o.className="dm-contents-title",o.textContent=r.title||"",e.appendChild(o),n.appendChild(e);const c=document.createElement("ol");c.className="dm-contents-list"+(r.numbered?" is-numbered":"");const f=[];for(const a of t){if(!a.id){const y=(a.textContent||"").toLowerCase().trim().replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,"")||"section";let C=y;for(let m=2;s.has(C);m++)C=`${y}-${m}`;s.add(C),a.id=C}const h=document.createElement("li");h.className=`dm-contents-item dm-contents-${a.tagName.toLowerCase()}`;const l=document.createElement("a");l.href=`#${a.id}`,l.textContent=a.textContent.trim(),h.appendChild(l),c.appendChild(h),f.push([a,l])}if(n.appendChild(c),u.replaceChildren(n),r.scrollSpy!==!1&&"IntersectionObserver"in window){const a=new Map,h=()=>{const y=f.find(([C])=>a.get(C))||null;f.forEach(([,C])=>{const m=y&&C===y[1];C.classList.toggle("is-active",!!m),m?C.setAttribute("aria-current","location"):C.removeAttribute("aria-current")})},l=new IntersectionObserver(y=>{y.forEach(C=>a.set(C.target,C.isIntersecting)),h()},{rootMargin:"-80px 0px -60% 0px"});f.forEach(([y])=>l.observe(y))}if(L?.classList.contains("contents-collapsible")){const a=document.createElement("button");a.type="button",a.className="dm-contents-toggle";const h=()=>{const l=L.classList.contains("contents-collapsed");a.setAttribute("aria-expanded",String(!l)),a.setAttribute("aria-label",l?"Show the contents":"Hide the contents"),a.title=a.getAttribute("aria-label"),a.innerHTML=`<span data-icon="${l?"list":"x"}"></span>`,window.Domma?.icons&&Domma.icons.scan(a)};a.addEventListener("click",()=>{S.set("contents_collapsed",L.classList.toggle("contents-collapsed")),h()}),h(),e.insertBefore(a,e.firstChild)}if(L?.classList.contains("contents-floating")&&r.movable){n.classList.add("is-movable");const a=S.get("contents_position");a&&Number.isFinite(a.x)&&Object.assign(u.style,{left:`${a.x}px`,top:`${a.y}px`,right:"auto",bottom:"auto"}),e.addEventListener("pointerdown",h=>{if(h.target.closest("button"))return;const l=u.getBoundingClientRect(),y=h.clientX-l.left,C=h.clientY-l.top,m=d=>{const p=Math.max(0,Math.min(window.innerWidth-l.width,d.clientX-y)),v=Math.max(0,Math.min(window.innerHeight-40,d.clientY-C));Object.assign(u.style,{left:`${p}px`,top:`${v}px`,right:"auto",bottom:"auto"})},w=()=>{document.removeEventListener("pointermove",m),S.set("contents_position",{x:parseInt(u.style.left,10),y:parseInt(u.style.top,10)})};document.addEventListener("pointermove",m),document.addEventListener("pointerup",w,{once:!0})})}window.E&&typeof E.contextMenu=="function"&&E.contextMenu(u,{items:()=>{const a=L?.classList.contains("contents-collapsible"),h=L?.classList.contains("contents-collapsed"),l=f.find(([,y])=>y.classList.contains("is-active"));return[{header:"Contents"},...a?[{label:h?"Show the contents":"Collapse to a button",icon:h?"list":"minimize",action:()=>u.querySelector(".dm-contents-toggle")?.click()}]:[],{label:"Copy a link to this section",icon:"link",disabled:!l,action:async()=>{const y=`${location.origin}${location.pathname}#${l[0].id}`;try{await _.copyToClipboard(y),E.toast("Link copied.",{type:"success"})}catch{E.toast("Could not copy the link.",{type:"error"})}}},{label:"Back to the top",icon:"arrow-up",action:()=>window.scrollTo({top:0,behavior:"smooth"})}]}}),L?.classList.contains("contents-autohide")&&(u.addEventListener("pointerdown",a=>{a.pointerType!=="mouse"&&!u.classList.contains("is-revealed")&&(u.classList.add("is-revealed"),a.preventDefault())}),document.addEventListener("pointerdown",a=>{u.contains(a.target)||u.classList.remove("is-revealed")}))}
|
|
1
|
+
import{applyMobileMenu as ne,initCarouselPopup as ae}from"/public/js/mobile-ui.mjs?v=20261016-mobile-options";import{installSmoothCarets as se}from"/public/js/smooth-caret.js";import{decodeBase64Json as V}from"/public/js/utf8-base64.mjs";import{makeBadgeEl as U,applyColour as G,applyPill as ie,applyFloat as re,applyHighlight as Y,applyItemHighlight as ce,itemHighlightAttrs as le,isCurrentItem as K,menuAlign as de,applyClasses as P,cssClasses as X,applyEntrance as J}from"/public/js/menu-decor.mjs?v=20261014-menu-entrance";import{applyMenuLook as me}from"/public/js/menu-cards.mjs?v=20261029-glider-layout";import{initPageMotion as ue}from"/public/js/page-motion.mjs?v=20261029-page-motion";import{applyFooterShowcase as pe}from"/public/js/footer-showcase.mjs?v=20261029-footer-showcase";import{initFloatingMenus as fe}from"/public/js/menu-float.js?v=20260829-float-panel";import{enhancePickerFields as Q,markInvalidFields as Z,showValidationPopup as ee}from"/public/js/forms.js?v=20261025-forms-files-total";import{buildWizardSteps as he}from"/public/js/form-steps.mjs";import{mountMotionControl as ge}from"/public/js/motion-control.mjs?v=20261029-motion-under-switcher";import{applyFormClasses as te,applySubmitButtons as ye,applyWizardButtons as ve}from"/public/js/form-submit.mjs";import{captureFileFields as oe,destroyDropzones as be,fileBlueprint as we,restoreFileFields as Ce,setFileLimits as Se,splitFiles as xe,submitBody as ke,totalTooLarge as Le,wireFileFields as $e}from"/public/js/form-files.mjs";document.addEventListener("click",m=>{const i=m.target?.closest?.(".dm-banner__dismiss");i&&(m.preventDefault(),i.closest(".dm-banner")?.remove())}),$(()=>{se(document);const m=window.__CMS_NAV__||{},i=window.__CMS_SITE__||{},M=i.brandingEnabled!==!1&&m.brand?.enabled!==!1;if(i.autoTheme?.enabled){let o=function(c){const d=(c||"07:00").split(":");return+d[0]*60+(+d[1]||0)},e=function(){const c=new Date,d=c.getHours()*60+c.getMinutes();return d>=o(s.dayStart)&&d<o(s.nightStart)?s.dayTheme:s.nightTheme},t=function(c){const d=s.apply&&s.apply[c]||{theme:c,custom:""};Domma.theme.set(d.theme);for(const n of[...document.body.classList])n.startsWith("dm-custom-")&&document.body.classList.remove(n);d.custom&&document.body.classList.add(d.custom)};const s=i.autoTheme;t(e()),setInterval(()=>t(e()),6e4)}const L=$("#site-navbar");if(L.length&&m.brand){let e=function(u,f=0){return Array.isArray(u)?u.filter(h=>!(h&&(h.type==="spacer"||h.type==="theme-switcher"))).filter(h=>!(h&&h.type==="separator"&&f===0)).map(h=>h&&h.type==="separator"?{divider:!0}:Array.isArray(h?.items)?{...h,items:e(h.items,f+1)}:h):u};const s={...m.brand},o=s.size&&s.size!=="md"?` navbar-brand-${s.size}`:"";if(s.logo||s.icon||s.tagline){let u="";s.logo?u+=`<img src="${s.logo}" class="navbar-brand-logo" alt="${s.text||""}">`:s.icon&&(u+=`<span data-icon="${s.icon}" style="width:1.1em;height:1.1em;margin-right:.35em;vertical-align:middle;"></span>`),s.text&&(u+=`<span class="navbar-brand-text${o}">${s.text}</span>`),s.tagline&&(u+=`<small class="navbar-brand-tagline">${s.tagline}</small>`),s.html=u}else o&&s.text&&(s.html=`<span class="navbar-brand-text${o}">${s.text}</span>`);const t=e(m.items||[]),c=m.position==="floating",d=m.orientation==="vertical";L.get(0).querySelector(":scope > .cms-nojs-links")?.remove(),Domma.elements.navbar("#site-navbar",{brand:M?s:null,items:t,variant:m.variant||"dark",position:c?"static":m.position||"sticky",collapsible:!0,appearOnHover:!!m.appearOnHover}),Domma.icons.scan("#site-navbar");const n=L.get(0);ne(n,m.mobile);const y=()=>{if(!d)return;const f=n.querySelector('a[aria-current="page"]')?.closest(".navbar-dropdown");f&&!f.classList.contains("open")&&f.querySelector(":scope > .navbar-dropdown-toggle")?.click()};n.classList.add(`dm-nav-align-${de(m)||"centre"}`),d&&L.get(0).classList.add("dm-nav-vertical",m.side==="right"?"dm-nav-vertical-right":"dm-nav-vertical-left");const g=m.dock||{};d&&!c&&Ee(n,m.side==="right"),!d&&g.hideOnScroll&&Ae(n),Y(n,m),J(n,m),P(n,m.class),c&&(re(L.get(0),m),fe(L.get(0).parentNode||document));const l=n.querySelector(".navbar-toggle"),b=n.querySelector(".navbar-collapse");if(l&&b){let u=null;const f=()=>{b.classList.contains("show")&&l.click()},h=()=>{u&&(u.remove(),u=null),document.body.classList.remove("dm-drawer-open")},p=()=>{u||(u=document.createElement("div"),u.className="dm-navbar-backdrop",u.addEventListener("click",f),document.body.appendChild(u),document.body.classList.add("dm-drawer-open"))};l.addEventListener("click",()=>{requestAnimationFrame(()=>{b.classList.contains("show")?(y(),p()):h()})}),b.addEventListener("click",k=>{k.target.closest("a")&&requestAnimationFrame(h)}),document.addEventListener("keydown",k=>{k.key==="Escape"&&b.classList.contains("show")&&f()})}const w=n.querySelector(".navbar-nav");if(w){const u=Array.from(w.children);let f=0;for(const h of m.items||[])if(!(h&&h.type==="separator")){if(h&&h.type==="spacer"){const p=document.createElement("li"),k=["xs","sm","md","lg","xl","flex"].includes(h.size)?h.size:"md";p.className=`dm-menu-spacer dm-menu-spacer--${k}`,p.setAttribute("aria-hidden","true"),w.insertBefore(p,u[f]||null);continue}if(h&&h.type==="theme-switcher"){const p=document.createElement("li");p.className="dm-menu-theme-switcher";const k=document.createElement("span");k.dataset.themeSwitcher="menu",k.dataset.position="inline",p.appendChild(k),w.insertBefore(p,u[f]||null);continue}f++}window.CMSThemeSwitcher?.scan(w)}n.querySelectorAll("[data-path]").forEach(u=>{const f=u.dataset.path.split(".").map(Number),h=f.length>1,p=f.reduce((q,r)=>q?.items?.[r],{items:t});if(!p)return;p.colour&&G(u,p.colour),p.pill&&!h&&ie(u,p.pill),ce(u,p),P(u.closest("li"),p.class);const k=U(p.badge);k&&u.appendChild(k),K(p.url,location.pathname)&&(u.classList.add("active"),u.setAttribute("aria-current","page"))}),me(n,m,t),y()}let I=null;const C=$("#site-footer");if(C.length){let c=function(r){return String(r??"").replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""")},d=function(r){return c(r).replace(/'/g,"'")},n=function(r){if(r&&r.type==="separator")return'<li class="footer-sep"><hr></li>';if(r&&r.type==="spacer")return`<li class="dm-menu-spacer dm-menu-spacer--${["xs","sm","md","lg","xl","flex"].includes(r.size)?r.size:"md"}" aria-hidden="true"></li>`;if(r&&r.type==="theme-switcher")return'<li class="dm-menu-theme-switcher"><span data-theme-switcher="menu" data-position="inline"></span></li>';const D=d(r.url||"#"),v=c(r.text||""),x=Array.isArray(r.items)&&r.items.length?`<ul>${r.items.map(n).join("")}</ul>`:"",N=le(r),O=[N.hover&&` data-menu-item-hover="${d(N.hover)}"`,N.active&&` data-menu-item-active="${d(N.active)}"`].filter(Boolean).join(""),j=N.accent?` style="--dm-menu-highlight:${d(N.accent)}"`:"",W=X(r.class).join(" ");return`<li${W?` class="${d(W)}"`:""}><a href="${D}"${O}${j}>${v}</a>${x}</li>`},g=function(){if(!i.footer)return"";const r=Object.keys(o).filter(v=>s[v]);if(!r.length)return"";const D=X(i.footer.socialClass).join(" ");return`<div class="footer-social">${r.map(v=>{const{label:x,svg:N}=o[v];return`<a href="${s[v]}" target="_blank" rel="noopener noreferrer" aria-label="${x}" class="footer-social-link${D?` ${d(D)}`:""}">${N}</a>`}).join("")}</div>`},u=function(r){if(!r||r.type==="separator"||r.type==="spacer")return"";if(r.type==="theme-switcher")return`<li class="cms-footer-col">${n(r).replace(/^<li[^>]*>|<\/li>$/g,"")}</li>`;const D=Array.isArray(r.items)&&r.items.length?`<ul>${r.items.map(n).join("")}</ul>`:"",v=w(r)?`<span class="cms-footer-col-title">${c(r.text||"")}</span>`:`<a class="cms-footer-col-title" href="${d(r.url||"#")}">${c(r.text||"")}</a>`,x=["cms-footer-col",...X(r.class)].join(" ");return`<li class="${d(x)}">${v}${D}</li>`},p=function(r,D,v=!1){return(r||[]).forEach(x=>{x&&(x.type==="separator"||x.type==="spacer"||x.type==="theme-switcher")||(v&&w(x)||D.push(x),Array.isArray(x.items)&&x.items.length&&p(x.items,D))}),D};const s=i.social||{},o={twitter:{label:"X / Twitter",svg:'<svg viewBox="0 0 24 24" fill="currentColor"><path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-4.714-6.231-5.401 6.231H2.742l7.73-8.835L1.254 2.25H8.08l4.259 5.629L18.244 2.25zm-1.161 17.52h1.833L7.084 4.126H5.117z"/></svg>'},facebook:{label:"Facebook",svg:'<svg viewBox="0 0 24 24" fill="currentColor"><path d="M24 12.073c0-6.627-5.373-12-12-12s-12 5.373-12 12c0 5.99 4.388 10.954 10.125 11.854v-8.385H7.078v-3.47h3.047V9.43c0-3.007 1.792-4.669 4.533-4.669 1.312 0 2.686.235 2.686.235v2.953H15.83c-1.491 0-1.956.925-1.956 1.874v2.25h3.328l-.532 3.47h-2.796v8.385C19.612 23.027 24 18.062 24 12.073z"/></svg>'},instagram:{label:"Instagram",svg:'<svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 2.163c3.204 0 3.584.012 4.85.07 3.252.148 4.771 1.691 4.919 4.919.058 1.265.069 1.645.069 4.849 0 3.205-.012 3.584-.069 4.849-.149 3.225-1.664 4.771-4.919 4.919-1.266.058-1.644.07-4.85.07-3.204 0-3.584-.012-4.849-.07-3.26-.149-4.771-1.699-4.919-4.92-.058-1.265-.07-1.644-.07-4.849 0-3.204.013-3.583.07-4.849.149-3.227 1.664-4.771 4.919-4.919 1.266-.057 1.645-.069 4.849-.069zM12 0C8.741 0 8.333.014 7.053.072 2.695.272.273 2.69.073 7.052.014 8.333 0 8.741 0 12c0 3.259.014 3.668.072 4.948.2 4.358 2.618 6.78 6.98 6.98C8.333 23.986 8.741 24 12 24c3.259 0 3.668-.014 4.948-.072 4.354-.2 6.782-2.618 6.979-6.98.059-1.28.073-1.689.073-4.948 0-3.259-.014-3.667-.072-4.947-.196-4.354-2.617-6.78-6.979-6.98C15.668.014 15.259 0 12 0zm0 5.838a6.162 6.162 0 100 12.324 6.162 6.162 0 000-12.324zM12 16a4 4 0 110-8 4 4 0 010 8zm6.406-11.845a1.44 1.44 0 100 2.881 1.44 1.44 0 000-2.881z"/></svg>'},linkedin:{label:"LinkedIn",svg:'<svg viewBox="0 0 24 24" fill="currentColor"><path d="M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433a2.062 2.062 0 01-2.063-2.065 2.064 2.064 0 112.063 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z"/></svg>'},github:{label:"GitHub",svg:'<svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12"/></svg>'},youtube:{label:"YouTube",svg:'<svg viewBox="0 0 24 24" fill="currentColor"><path d="M23.495 6.205a3.007 3.007 0 00-2.088-2.088c-1.87-.501-9.396-.501-9.396-.501s-7.507-.01-9.396.501A3.007 3.007 0 00.527 6.205a31.247 31.247 0 00-.522 5.805 31.247 31.247 0 00.522 5.783 3.007 3.007 0 002.088 2.088c1.868.502 9.396.502 9.396.502s7.506 0 9.396-.502a3.007 3.007 0 002.088-2.088 31.247 31.247 0 00.5-5.783 31.247 31.247 0 00-.5-5.805zM9.609 15.601V8.408l6.264 3.602z"/></svg>'}};let e='<div class="footer-inner container">';const t=window.__CMS_FOOTER__||{primary:[],legal:[],copyright:""},y=(()=>{const r=i.cookieConsent;if(!r||r.showSettingsLink===!1)return"";const D=v=>(v||[]).some(x=>x&&(x.url==="#cookie-settings"||D(x.items)));return D(t.primary)||D(t.legal)?"":`<li class="cms-cookie-settings"><a href="#cookie-settings">${c(r.settingsLinkText||"Cookie settings")}</a></li>`})(),l=["columns","row"].includes(i.footer?.layout)?i.footer.layout:"auto",b=i.footer?.look==="showcase"||l==="columns"||l==="auto"&&t.primary.some(r=>Array.isArray(r?.items)&&r.items.length),w=r=>b&&!r.url&&Array.isArray(r.items)&&r.items.length>0;if(b){const r=m.brand||{},D=M&&(r.text||i.title)||"",v=M?r.logo?`<img src="${d(r.logo)}" class="cms-footer-brand-logo" alt="">`:r.icon?'<span class="cms-footer-brand-icon"></span>':"":"",x=String(i.footer?.blurb||"").trim();e='<div class="footer-inner footer-inner--columns container"><div class="cms-footer-top">',e+=`<div class="cms-footer-brand">${D||v?`<a class="cms-footer-brand-name" href="${d(r.url||"/")}">${v}<span>${c(D)}</span></a>`:""}${x?`<p class="cms-footer-blurb">${c(x)}</p>`:""}${g()}</div>`,t.primary.length&&(e+=`<nav class="footer-nav footer-nav--primary footer-nav--columns" aria-label="Footer"><ul>${t.primary.map(u).join("")}</ul></nav>`),e+='</div><div class="cms-footer-bottom">',t.copyright&&(e+=`<p class="cms-footer-copyright">${c(t.copyright)}</p>`),(t.legal.length||y)&&(e+=`<nav class="footer-nav footer-nav--legal" aria-label="Legal"><ul>${t.legal.map(n).join("")}${y}</ul></nav>`),e+="</div>"}else t.copyright&&(e+=`<p>${c(t.copyright)}</p>`),t.primary.length&&(e+=`<nav class="footer-nav footer-nav--primary"><ul>${t.primary.map(n).join("")}</ul></nav>`),(t.legal.length||y)&&(e+=`<nav class="footer-nav footer-nav--legal"><ul>${t.legal.map(n).join("")}${y}</ul></nav>`),e+=g();e+="</div>",C.get(0).querySelector(":scope > .cms-nojs-links")?.remove();const f=C.get(0).querySelector(":scope > .cms-footer-blocks");f?.remove(),C.html(e),f&&C.get(0).prepend(f),window.CMSThemeSwitcher?.scan(C.get(0));const h=C.get(0).querySelector(".cms-footer-brand-icon");h&&m.brand?.icon&&h.setAttribute("data-icon",m.brand.icon),P(C.get(0),i.footer?.class),b&&pe(C.get(0),i);const k=[...p(t.primary,[],!0),...p(t.legal,[])];C.get(0).querySelectorAll(".footer-nav a").forEach((r,D)=>{const v=k[D];if(!v)return;v.colour&&G(r,v.colour);const x=U(v.badge);x&&r.appendChild(x),K(v.url,location.pathname)&&(r.classList.add("is-active"),r.setAttribute("aria-current","page"))}),Y(C.get(0).querySelector(".footer-nav--primary"),{highlight:t.primaryHighlight}),Y(C.get(0).querySelector(".footer-nav--legal"),{highlight:t.legalHighlight}),J(C.get(0).querySelector(".footer-nav--primary"),{entrance:t.primaryEntrance}),J(C.get(0).querySelector(".footer-nav--legal"),{entrance:t.legalEntrance}),P(C.get(0).querySelector(".footer-nav--primary"),t.primaryClass),P(C.get(0).querySelector(".footer-nav--legal"),t.legalClass),I=C.get(0).querySelector(".cms-footer-bottom")||C.get(0).querySelector(".footer-inner")}ge(i.motion||{enabled:i.footer?.showMotionToggle!==!1,default:i.footer?.motionDefault,placement:"footer",control:i.footer?.motionControl,label:"Reduce motion"},{footer:I,navbar:document.querySelector("#site-navbar .navbar-container")?.closest("#site-navbar")||null});const A=document.getElementById("site-sidebar");A&&Te(A),Domma.icons.scan(),Domma.syntax?.scan&&Domma.syntax.scan();const a=$(".page-body");if(a.length){a.find(".accordion").each(function(){Domma.elements.accordion(this,{allowMultiple:this.dataset.multi==="true"})}),a.find(".tabs").each(function(){Domma.elements.tabs(this)}),a.find(".carousel").each(function(){const o={primary:"--dm-primary",secondary:"--dm-secondary",accent:"--dm-primary",text:"--dm-text",surface:"--dm-surface",background:"--dm-background",success:"--dm-success",warning:"--dm-warning",danger:"--dm-danger",info:"--dm-info"},e=g=>{const l=String(g||"").trim();return l?o[l.toLowerCase()]?`var(${o[l.toLowerCase()]})`:CSS.supports("color",l)?l:"":""},t=e(this.dataset.background),c=e(this.dataset.arrowBackground),d=e(this.dataset.arrowColor);t&&(this.style.setProperty("--dm-carousel-background",t),this.classList.add("dm-carousel-custom-background")),c&&this.style.setProperty("--dm-carousel-arrow-background",c),d&&this.style.setProperty("--dm-carousel-arrow-color",d);const n=["top","middle","bottom"].includes(this.dataset.arrowPosition)?this.dataset.arrowPosition:"middle";this.classList.add(`dm-carousel-arrows-${n}`);const y={autoplay:this.dataset.autoplay==="true",interval:parseInt(this.dataset.interval,10)||5e3,loop:this.dataset.loop!=="false",animation:this.dataset.animation||"slide",heightMode:this.dataset.heightMode==="average"?"average":"crop"};if(this.dataset.height){const g=Number(this.dataset.height);Number.isFinite(g)&&g>0&&(y.height=g)}if(this.dataset.animationDuration){const g=parseInt(this.dataset.animationDuration,10);Number.isFinite(g)&&(y.animationDuration=g)}this.dataset.animationEasing&&(y.animationEasing=this.dataset.animationEasing),this.dataset.ctx&&(this._dmCtxSlides=Array.from(this.querySelectorAll(".carousel-slide")).map(g=>g.cloneNode(!0)),this._dmCarouselOpts=y),this._dmCarousel=Domma.elements.carousel(this,y),ae(this)}),a.find(".dm-countdown").each(function(){const o={autoStart:!0},e=String(this.dataset.format||"").trim(),t=String(this.dataset.to||"").trim(),c=/^(\d{4})-(\d{2})-(\d{2})$/.exec(t),d=c?new Date(+c[1],+c[2]-1,+c[3]).getTime():t?new Date(t).getTime():NaN;Number.isFinite(d)?o.duration=Math.max(0,d-Date.now()):this.dataset.duration&&(o.duration=(parseInt(this.dataset.duration,10)||0)*1e3);const n=/^dd:/i.test(e);o.format=n?"hh:mm:ss":e||(o.duration>=36e5?"hh:mm:ss":"mm:ss");const y=this,g=b=>String(b).padStart(2,"0"),l=b=>{const w=Math.max(0,Math.floor(b/1e3)),u=y.querySelector(".dm-timer-time");u&&(u.textContent=`${g(Math.floor(w/86400))}:${g(Math.floor(w%86400/3600))}:${g(Math.floor(w%3600/60))}:${g(w%60)}`)};n&&(o.onTick=l),Domma.elements.timer(this,o),n&&l(o.duration||0)}),a.find("[data-tooltip]:not(.dm-range-slider)").each(function(){Domma.elements.tooltip(this,{content:$(this).data("tooltip"),position:$(this).data("tooltip-position")||"top"})}),a.find(".dm-progression").each(function(){const o=Array.from(this.querySelectorAll(".dm-progression-item")).map(c=>{const d={title:c.querySelector(".dm-progression-item-title")?.textContent?.trim()||"",description:c.querySelector(".dm-progression-item-body")?.innerHTML?.trim()||""};return c.dataset.date&&(d.date=c.dataset.date),c.dataset.status&&(d.status=c.dataset.status),c.dataset.eventIcon&&(d.icon=c.dataset.eventIcon),c.dataset.entryId&&(d._entryId=c.dataset.entryId),d}),e=this.dataset.layout==="centred"?"centered":this.dataset.layout||"vertical",t=Domma.elements.progression(this,{layout:e,theme:this.dataset.theme||"minimal",mode:this.dataset.mode||"timeline",statusIcons:!0,allowHtml:!0,...o.length?{items:o}:{}});if((this.dataset.mode||"timeline")==="timeline"){const c={completed:{label:"Completed",icon:"check-circle",tone:"success"},planned:{label:"Planned",icon:"circle",tone:"secondary"},"in-progress":{label:"In progress",icon:"clock",tone:"primary"},blocked:{label:"Blocked",icon:"alert-circle",tone:"danger"}};this.querySelectorAll(".dm-progression-item").forEach((d,n)=>{const y=o[n];if(!y)return;const g=d.querySelector(".dm-progression-title");if(y.icon&&g){const b=document.createElement("span");b.dataset.icon=y.icon,b.style.cssText="display:inline-flex;vertical-align:middle;margin-right:.4em",b.setAttribute("aria-hidden","true"),g.prepend(b)}const l=c[y.status];if(l&&g){const b=document.createElement("span");b.className=`badge badge-${l.tone} cms-timeline-status`,b.style.cssText="display:inline-flex;align-items:center;gap:.35em;margin:.4rem 0";const w=document.createElement("span");w.dataset.icon=l.icon,w.dataset.iconSize="14",w.setAttribute("aria-hidden","true"),b.append(w,document.createTextNode(l.label)),g.after(b)}})}Domma.icons.scan(this),o.length&&(this._dmCtx={instance:t,items:o})});try{Domma.effects.reveal(".page-body .hero",{animation:"slide-up",duration:480,threshold:.06,stagger:60,once:!1})}catch{}const s=matchMedia("(prefers-reduced-motion: reduce)").matches||document.documentElement.classList.contains("dm-reduced-motion");document.querySelectorAll(".page-body .row[data-reveal]").forEach(o=>{const e=o.dataset.revealMode||"stagger",t=o.dataset.revealAnimation||"slide-up",c=parseInt(o.dataset.revealDuration,10)||(t.startsWith("swoosh")?700:400),d=parseInt(o.dataset.revealStagger,10)||(t.startsWith("swoosh")?90:60),n=parseInt(o.dataset.revealDelay,10)||0,y=o.dataset.revealDirection==="rtl",g=o.dataset.revealReplay==="true",l=Array.from(o.children);if(s&&t.startsWith("swoosh"))return;const b={"slide-up":"translateY(30px)","slide-down":"translateY(-30px)","slide-left":"translateX(30px)","slide-right":"translateX(-30px)",zoom:"scale(0.85)",flip:"perspective(600px) rotateX(15deg)",swoosh:"translateY(36px) scale(0.96)"},w=p=>t!=="swoosh-sides"?b[t]||"":l.length>1&&p===0?"translateX(-64px) rotate(-1.5deg)":l.length>1&&p===l.length-1?"translateX(64px) rotate(1.5deg)":"translateY(40px) scale(0.94)",u=t.startsWith("swoosh"),f=u?"cubic-bezier(.2,.8,.2,1)":"ease",h=(p,k)=>{p.style.opacity="0",p.style.transform=w(k),u&&(p.style.filter="blur(10px)")};l.forEach((p,k)=>{h(p,k),p.style.transition=`opacity ${c}ms ${f}, transform ${c}ms ${f}`+(u?`, filter ${c}ms ${f}`:"");const q=y?l.length-1-k:k;p.style.transitionDelay=e==="stagger"?`${n+q*d}ms`:`${n}ms`}),requestAnimationFrame(()=>requestAnimationFrame(()=>{const p=new IntersectionObserver(k=>{k.forEach(q=>{if(q.isIntersecting){if(q.target.offsetWidth,q.target.style.opacity="1",q.target.style.transform="",u&&(q.target.style.filter=""),!g){p.unobserve(q.target);const r=q.target;r.addEventListener("transitionend",function D(v){v.target!==r||v.propertyName!=="opacity"||(r.removeEventListener("transitionend",D),r.style.transition="",r.style.transitionDelay="")})}}else g&&q.boundingClientRect.top>0&&h(q.target,l.indexOf(q.target))})},{threshold:.1});l.forEach(k=>p.observe(k))}))}),ue(document.querySelector(".page-body")||document),a.find(".card[data-collapsible]").each(function(){const o=this.querySelector(".card-header");o&&o.addEventListener("click",()=>this.classList.toggle("is-collapsed"))}),a.find(".dm-so-trigger").each(function(){this.addEventListener("click",()=>{const o=this.dataset.soTarget,e=document.getElementById(o);if(!e)return;const t=E.slideover({title:e.dataset.soTitle||"",size:e.dataset.soSize||"md",position:e.dataset.soPosition||"right"});e.style.display="",t.setContent(e),t.open()})})}if(typeof $.setup=="function"){const s=Object.assign({},window.__CMS_DCONFIG__||{});if(document.querySelectorAll(".dm-page-config[data-config]").forEach(o=>{try{const e=V(o.dataset.config);Object.assign(s,e)}catch{}}),Object.keys(s).length>0){const o={};for(const[e,t]of Object.entries(s)){const c=t?.events?.click,{confirm:d,toast:n,alert:y,prompt:g,...l}=c||{};if(d||n||y||g){$(e).on("click",async function(W){if(W.preventDefault(),d&&!await E.confirm(d))return;let z=null;if(!(g&&(z=await E.prompt(g,{inputPlaceholder:l.promptPlaceholder||"",inputValue:l.promptDefault||""}),z===null))){{const B=$(l.target||this);l.toggleClass&&B.toggleClass(l.toggleClass),l.addClass&&B.addClass(l.addClass),l.removeClass&&B.removeClass(l.removeClass),z!==null&&(l.setText&&B.text(z),l.setVal&&B.val(z),l.setAttr&&B.attr(l.setAttr,z))}l.href&&(window.location.href=l.href),n&&E.toast(n,{type:l.toastType||"success"}),y&&E.alert(y)}});const{target:w,toggleClass:u,addClass:f,removeClass:h,setText:p,setVal:k,setAttr:q,href:r,toastType:D,promptPlaceholder:v,promptDefault:x,...N}=l,O={...t.events};Object.keys(N).length?O.click=w?{target:w,...N}:N:delete O.click;const j={...t,events:O};Object.keys(O).length||delete j.events,Object.keys(j).length&&(o[e]=j)}else o[e]=t}$.setup(o)}}a.length&&R(a.get(0))}),window.wireCTAButtons=R;function R(m){m.querySelectorAll(".dm-cta-trigger").forEach(i=>{i.__ctaWired||(i.__ctaWired=!0,i.addEventListener("click",async()=>{const M=i.dataset.action,L=i.dataset.entry,I=i.dataset.confirm;let C=S.get("auth_token");if(!C){E.toast("Please log in to perform this action.",{type:"warning"});return}if(I&&!await E.confirm(I))return;const A=Array.from(i.childNodes).map(s=>s.cloneNode(!0));i.disabled=!0,i.textContent="Running\u2026";const a=s=>fetch(`/api/actions/${M}/public`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${s}`},body:JSON.stringify(i.dataset.scope?{entryId:L,scope:i.dataset.scope}:{entryId:L})});try{let s=await a(C);if(s.status===401){const e=S.get("auth_refresh_token");if(e){const t=await fetch("/api/auth/refresh",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({refreshToken:e})});if(t.ok){const{token:c}=await t.json();S.set("auth_token",c),C=c,s=await a(C)}}}const o=await s.json().catch(()=>({}));if(!s.ok||o.success===!1)throw new Error(o.error||o.message||`Error ${s.status}`);E.toast(o.message||"Action completed.",{type:"success"})}catch(s){E.toast(s.message||"Action failed.",{type:"error"})}finally{i.disabled=!1,i.textContent="",A.forEach(s=>i.appendChild(s)),Domma.icons.scan(i)}}))})}(function(){const i=document.querySelectorAll('.dm-collection-hydrate[data-collection-scope="mine"]');if(!i.length)return;const M=S.get("auth_token");function L(C,A){const a=document.createElement("div");a.className="dm-collection-display dm-collection-empty";const s=document.createElement("p");if(A){s.appendChild(document.createTextNode("Please "));const o=document.createElement("a");o.href="/login",o.textContent="sign in",s.appendChild(o),s.appendChild(document.createTextNode(" to view your entries."))}else s.textContent=C;return a.appendChild(s),a}function I(C,A){const a=document.createElement("template");a.innerHTML=A||"",C.replaceWith(a.content)}i.forEach(async C=>{const A=C.dataset.collectionAttrs;if(!A)return;if(!M){C.replaceChildren(L("",!0));return}const a=s=>fetch("/api/collections/render-scope",{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${s}`},body:JSON.stringify({attrs:A})});try{let s=M,o=await a(s);if(o.status===401){const t=S.get("auth_refresh_token");if(t){const c=await fetch("/api/auth/refresh",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({refreshToken:t})});if(c.ok){const{token:d}=await c.json();S.set("auth_token",d),s=d,o=await a(s)}}}if(!o.ok){const{error:t}=await o.json().catch(()=>({}));C.replaceChildren(L(t||"Unable to load entries.",!1));return}const{html:e}=await o.json();I(C,e),window.DommaCollectionBrowser?.hydrate&&window.DommaCollectionBrowser.hydrate(document)}catch{C.replaceChildren(L("Unable to load entries.",!1))}}),setTimeout(()=>{typeof Domma<"u"&&Domma.icons?.scan&&Domma.icons.scan(),typeof Domma<"u"&&Domma.syntax?.scan&&Domma.syntax.scan(),R(document.body)},100)})(),(function(){const i=document.querySelectorAll("[data-collection-table]");!i.length||typeof T>"u"||!T.create||i.forEach(M=>{let L;try{L=V(M.dataset.payload)}catch{return}const{columns:I,rows:C,search:A,sortable:a,exportable:s,pageSize:o,empty:e,ctaConfig:t,slug:c,exportCfg:d}=L;if(!I?.length)return;if(t){const g=I.findIndex(l=>l.key==="_cta");g!==-1&&(I[g]={key:"_cta",title:"",render:(l,b)=>{const w=document.createElement("button");if(w.className=`btn btn-${t.style||"primary"} dm-cta-trigger`,w.dataset.action=t.action||"",t.scope&&(w.dataset.scope=t.scope),w.dataset.entry=b._entryId||"",t.confirm&&(w.dataset.confirm=t.confirm),t.icon){const u=document.createElement("span");u.dataset.icon=t.icon,w.appendChild(u),w.appendChild(document.createTextNode(" "))}return w.appendChild(document.createTextNode(t.label||"Run")),w}})}const n="col-table-"+Math.random().toString(36).slice(2,7),y=document.createElement("div");y.id=n,M.replaceChildren(y),T.create("#"+n,{data:C,columns:I,search:A,sortable:a,exportable:s,pageSize:o,emptyMessage:e}),t&&R(y),M._dmTable={columns:I,rows:C,slug:c,exportCfg:d,render(g){T.create("#"+n,{data:g,columns:I,search:A,sortable:a,exportable:s,pageSize:o,emptyMessage:e}),t&&R(y)}}})})(),(function(){const i=document.querySelectorAll("[data-form-inline]");if(!i.length||typeof F>"u")return;function M(a){const s=a&&a.logic;return!!(s&&(s.visibility||s.requirement))}function L(a,s){const o={};return(a||[]).forEach(e=>{if(e.type==="page-break"||e.type==="spacer"||!e.name)return;const t=e.type==="chooser"||!!(e.variant&&["select","radio","checkbox-group"].includes(e.type)),c=e.type==="checkbox"?"boolean":e.type==="date"||e.type==="icon"||e.type==="image"?"string":t?"chooser":e.type,d={...e.formConfig||{}};d.span==="full"&&s&&(d.span=s);const n={type:c,required:M(e)?!1:e.required,label:e.label,options:e.options,formConfig:{...e.placeholder&&{placeholder:e.placeholder},...e.helper&&{hint:e.helper},...e.tooltip&&{tooltip:e.tooltip},...d}};e.minLength!==void 0&&(n.minLength=e.minLength),e.maxLength!==void 0&&(n.maxLength=e.maxLength),e.min!==void 0&&(n.min=e.min),e.max!==void 0&&(n.max=e.max),t&&(e.variant&&(n.variant=e.variant),(e.multiple||e.type==="checkbox-group")&&(n.multiple=!0),e.density&&(n.density=e.density),e.columns&&(n.columns=e.columns),e.accent&&(n.accent=e.accent),e.accentStyle&&(n.accentStyle=e.accentStyle),e.glow&&(n.glow=!0),e.glowColour&&(n.glowColour=e.glowColour),e.shadow&&(n.shadow=e.shadow),e.shadowColour&&(n.shadowColour=e.shadowColour)),o[e.name]=e.type==="file"?we(e,n):n}),o}function I(a){const s={};return(a||[]).forEach(o=>{if(!(!o.name||o.type==="page-break"||o.type==="spacer")&&(o.type==="select"||o.type==="multiselect")&&o.required){const e=(o.options||[])[0];e&&(s[o.name]=typeof e=="object"?e.value:e)}}),s}function C(a,s){(s||[]).forEach(o=>{if(o.type!=="date"||!o.name)return;const e=a.querySelector(`[name="${o.name}"]`);e&&e.type!=="date"&&(e.type="date")})}function A(a,s,o){let e=a.querySelector(".cms-form-message");e||(e=document.createElement("p"),e.className="cms-form-message",a.appendChild(e)),e.textContent=s,e.style.cssText=o?"color:var(--dm-danger);margin-top:.75rem;":"color:var(--dm-success);margin-top:.75rem;"}i.forEach(a=>{let s;try{s=V(a.dataset.formInline)}catch{return}a.querySelector(":scope > .cms-form-embed-summary")?.remove();const o=s.fields||[],e=s.settings||{},t=Number(e.columns)===3?3:2,c=e.layout||"grid",d=o.some(u=>u.type==="page-break"),n=new Map;Se(a,s.fileLimits,n),$e(a,o);const y=async u=>{if(a.dataset.triggerBlocked==="true"){const f=a._formLogicRuntime,h=f&&f.submitBlock?f.submitBlock().reasons:[];A(a,h.join(" ")||"This form cannot be submitted yet.",!0);const p=new Error("Blocked by a form trigger");throw p.formSubmitFailed=!0,p}try{const f=a.querySelector(".fb-form-honeypot"),h=f?.querySelector('input[type="text"]')||null,p=f?.querySelector('[name="_t"]')||null,k=Object.assign({},u);delete k._t,f?.dataset.hpName&&delete k[f.dataset.hpName],h!==null&&(k._hp=h.value),p!==null&&(k._t=p.value);const q=new Map(n);oe(a,q);const r=xe(k,o,q),D=Le(a,r.files);if(D){A(a,D,!0);const x=new Error(D);throw x.formSubmitFailed=!0,x}const v=await H.post(`/api/forms/submit/${s.slug}`,r.files.size?ke(r.values,r.files).body:r.values);if(v&&v.error){A(a,v.error,!0);const x=new Error(v.error);throw x.formSubmitFailed=!0,x}if(v?.redirect){window.location.href=v.redirect;return}for(be(a);a.firstChild;)a.removeChild(a.firstChild);A(a,v?.message||e.successMessage||"Thank you for your submission.",!1)}catch(f){throw f.formSubmitFailed||A(a,f.message||"Submission failed. Please try again.",!0),f}},g=Date.now();function l(u){const f=u.querySelector("form");if(!f||f.querySelector(".fb-form-honeypot"))return;const h=document.createElement("div");h.className="fb-form-honeypot",h.setAttribute("aria-hidden","true");const p=document.createElement("input");p.name=f.querySelector('[name="website"]')?"_website":"website",h.dataset.hpName=p.name,p.type="text",p.tabIndex=-1,p.autocomplete="url",p.placeholder="https://",h.appendChild(p);const k=document.createElement("input");k.name="_t",k.type="hidden",k.value=g,h.appendChild(k),f.appendChild(h)}function b(u){if(!window.FormLogicEngine||!o.some(h=>h.logic||Array.isArray(h.triggers)&&h.triggers.length))return null;const f=new window.FormLogicEngine.FormLogicRuntime(s,a,u?{baseValues:u}:void 0);if(f.init(),a._formLogicRuntime=f,a._formLogicObserved)return f;if(a._formLogicObserved=!0,a.parentNode&&typeof MutationObserver<"u"){const h=new MutationObserver(function(p){for(const k of p)for(const q of k.removedNodes)if(q===a||q.nodeType===1&&q.contains&&q.contains(a)){a._formLogicRuntime&&a._formLogicRuntime.destroy(),h.disconnect();return}});h.observe(a.parentNode,{childList:!0,subtree:!1}),a._formLogicObserver=h}return f}let w=!1;if(a.addEventListener("dm-form-end",u=>{const f=u.detail?.validation;if(f&&!f.valid){Z(a,f.missing),ee(f);return}w||(w=!0,y(u.detail?.values||{}).catch(()=>{}).finally(()=>{w=!1}))}),d&&F.wizard){const u=he(o,s.title),f=u.map(v=>({title:v.title,description:v.description,fields:L(v.fields,t)})),h=u.map(v=>v.fields);let p=0,k=null,q=null;const r=v=>{const x=window.FormLogicEngine;if(!x||!x.validateFields)return;const N=x.validateFields(a,h[p]||[]);N.valid||(v.preventDefault(),v.stopImmediatePropagation(),Z(a,N.missing),ee(N))},D=()=>{const v=h[p]||o;ve(a,e),te(a,e,v),C(a,v),Q(a,v),Ce(a,n),e.honeypot!==!1&&l(a),k&&k.destroy(),k=b(q?.getStepData?q.getStepData():void 0),a.querySelectorAll(".wizard-next, .wizard-submit").forEach(x=>x.addEventListener("click",r,!0))};q=F.wizard(a,{schema:{steps:f},layout:c,columns:t,finishText:e.submitText||"Finish",onSubmit:y,onStepChange:v=>{oe(a,n),p=v,setTimeout(D,0)}}),a.addEventListener("dm-form-trigger",v=>{if(v.detail?.type!=="goto-step"||!q?.goToStep)return;const x=Number(v.detail.action.step);!Number.isFinite(x)||x<0||x>=f.length||x!==p&&q.goToStep(x)}),Promise.resolve(q).then(D)}else if(F.render){const u=F.render(a,L(o,t),I(o),{submitText:e.submitText||"Submit",layout:c,columns:t,onSubmit:y});Promise.resolve(u).then(function(){ye(a,e,c),te(a,e,o),C(a,o),Q(a,o),e.honeypot!==!1&&l(a),b()})}})})();function Ee(m,i){const M=document.body,L=M.classList,I=new Set((M.dataset.railSite||"").split(" ").filter(Boolean)),C=()=>window.matchMedia("(min-width: 993px)").matches;m.querySelectorAll(".navbar-link, .navbar-dropdown-toggle").forEach(t=>{const c=t.textContent.trim();c&&(t.setAttribute("title",c),t.querySelector("svg, [data-icon]")||(t.dataset.initial=c.charAt(0).toUpperCase()))});const A=document.createElement("button");A.type="button",A.className="dm-nav-collapse-toggle";const a=()=>{const t=L.contains("dm-vnav-collapsed");A.setAttribute("aria-pressed",String(t)),A.setAttribute("aria-label",t?"Expand menu":"Collapse menu"),A.title=A.getAttribute("aria-label");const c=i?"chevrons-left":"chevrons-right",d=i?"chevrons-right":"chevrons-left";A.innerHTML=`<span data-icon="${t?c:d}"></span>`,window.Domma&&Domma.icons&&Domma.icons.scan(A)},s=t=>{L.toggle("dm-vnav-collapsed",t),S.set("nav_collapsed",t),a()};A.addEventListener("click",()=>s(!L.contains("dm-vnav-collapsed"))),a();const o=m.querySelector(".navbar-container")||m;o.insertBefore(A,o.firstChild?.nextSibling||null);const e=t=>{L.toggle("dm-vnav-autohide",t),L.toggle("dm-vnav-collapsible",!t&&I.has("collapsible")),t?L.remove("dm-vnav-collapsed"):I.has("collapsible")&&S.get("nav_collapsed")===!0&&L.add("dm-vnav-collapsed"),m.classList.remove("dm-nav-revealed"),a()};m.addEventListener("pointerdown",t=>{L.contains("dm-vnav-autohide")&&t.pointerType!=="mouse"&&!m.classList.contains("dm-nav-revealed")&&(m.classList.add("dm-nav-revealed"),t.preventDefault())}),document.addEventListener("pointerdown",t=>{m.contains(t.target)||m.classList.remove("dm-nav-revealed")}),window.E&&typeof E.contextMenu=="function"&&E.contextMenu(m,{items:()=>{const t=L.contains("dm-vnav-autohide"),c=L.contains("dm-vnav-collapsed"),d=S.has("nav_autohide")||S.has("nav_collapsed");return[{header:"Menu"},{label:c?"Expand menu":"Collapse to icons",icon:c?"maximize":"minimize",disabled:!C()||t||!I.has("collapsible"),action:()=>s(!c)},{label:t?"Keep on screen":"Hide when not in use",icon:t?"eye":"eye-off",disabled:!C(),action:()=>{S.set("nav_autohide",!t),e(!t)}},{type:"divider"},{label:"Reset to the site's setting",icon:"refresh",disabled:!d,action:()=>{S.remove("nav_autohide"),S.remove("nav_collapsed"),e(I.has("autohide")),L.toggle("dm-vnav-collapsed",!I.has("autohide")&&I.has("start-collapsed")),a()}}]}})}function Ae(m){m.classList.add("dm-nav-scroll-hide");let i=window.scrollY,M=!1;window.addEventListener("scroll",()=>{M||(M=!0,requestAnimationFrame(()=>{M=!1;const L=window.scrollY,I=m.matches(":focus-within")||m.querySelector(".navbar-dropdown.open, .navbar-collapse.show");L>i&&L>m.offsetHeight&&!I?m.classList.add("dm-nav-scrolled-away"):L<i&&m.classList.remove("dm-nav-scrolled-away"),i=L}))},{passive:!0})}function Te(m){let i={};try{i=JSON.parse(m.dataset.contents||"{}")}catch{}const M=m.closest(".site-main"),L=document.querySelector(".site-content"),I=new Set(String(i.exclude||"").split(",").map(n=>n.trim().toLowerCase()).filter(Boolean)),C=n=>!n.closest(".hero, .card, .accordion, .tabs, figure, #site-sidebar"),A=n=>(L?[...L.querySelectorAll(n)]:[]).filter(C).filter(y=>!I.has(y.textContent.trim().toLowerCase())),a=i.headings==="h2"?A("h2"):i.headings==="h3"?A("h3"):i.headings==="h2h3"?A("h2, h3"):A("h2").length?A("h2"):A("h3");if(!a.length){M?.classList.add("no-sidebar-items");return}const s=new Set([...document.querySelectorAll("[id]")].map(n=>n.id)),o=document.createElement("nav");o.className="dm-contents",o.setAttribute("aria-label",i.title||"Page contents"),i.hover&&(o.dataset.menuHover=i.hover),i.selected&&(o.dataset.menuActive=i.selected),i.accent&&o.style.setProperty("--dm-menu-highlight",i.accent.startsWith("#")?i.accent:`var(--dm-${i.accent})`),i.font&&(o.style.fontFamily=i.font),i.fontSize&&(o.style.fontSize=i.fontSize),i.fontWeight&&(o.style.fontWeight=i.fontWeight);const e=document.createElement("div");e.className="dm-contents-head";const t=document.createElement("p");t.className="dm-contents-title",t.textContent=i.title||"",e.appendChild(t),o.appendChild(e);const c=document.createElement("ol");c.className="dm-contents-list"+(i.numbered?" is-numbered":"");const d=[];for(const n of a){if(!n.id){const l=(n.textContent||"").toLowerCase().trim().replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,"")||"section";let b=l;for(let w=2;s.has(b);w++)b=`${l}-${w}`;s.add(b),n.id=b}const y=document.createElement("li");y.className=`dm-contents-item dm-contents-${n.tagName.toLowerCase()}`;const g=document.createElement("a");g.href=`#${n.id}`,g.textContent=n.textContent.trim(),y.appendChild(g),c.appendChild(y),d.push([n,g])}if(o.appendChild(c),m.replaceChildren(o),i.scrollSpy!==!1&&"IntersectionObserver"in window){const n=new Map,y=()=>{const l=d.find(([b])=>n.get(b))||null;d.forEach(([,b])=>{const w=l&&b===l[1];b.classList.toggle("is-active",!!w),w?b.setAttribute("aria-current","location"):b.removeAttribute("aria-current")})},g=new IntersectionObserver(l=>{l.forEach(b=>n.set(b.target,b.isIntersecting)),y()},{rootMargin:"-80px 0px -60% 0px"});d.forEach(([l])=>g.observe(l))}if(M?.classList.contains("contents-collapsible")){const n=document.createElement("button");n.type="button",n.className="dm-contents-toggle";const y=()=>{const g=M.classList.contains("contents-collapsed");n.setAttribute("aria-expanded",String(!g)),n.setAttribute("aria-label",g?"Show the contents":"Hide the contents"),n.title=n.getAttribute("aria-label"),n.innerHTML=`<span data-icon="${g?"list":"x"}"></span>`,window.Domma?.icons&&Domma.icons.scan(n)};n.addEventListener("click",()=>{S.set("contents_collapsed",M.classList.toggle("contents-collapsed")),y()}),y(),e.insertBefore(n,e.firstChild)}if(M?.classList.contains("contents-floating")&&i.movable){o.classList.add("is-movable");const n=S.get("contents_position");n&&Number.isFinite(n.x)&&Object.assign(m.style,{left:`${n.x}px`,top:`${n.y}px`,right:"auto",bottom:"auto"}),e.addEventListener("pointerdown",y=>{if(y.target.closest("button"))return;const g=m.getBoundingClientRect(),l=y.clientX-g.left,b=y.clientY-g.top,w=f=>{const h=Math.max(0,Math.min(window.innerWidth-g.width,f.clientX-l)),p=Math.max(0,Math.min(window.innerHeight-40,f.clientY-b));Object.assign(m.style,{left:`${h}px`,top:`${p}px`,right:"auto",bottom:"auto"})},u=()=>{document.removeEventListener("pointermove",w),S.set("contents_position",{x:parseInt(m.style.left,10),y:parseInt(m.style.top,10)})};document.addEventListener("pointermove",w),document.addEventListener("pointerup",u,{once:!0})})}window.E&&typeof E.contextMenu=="function"&&E.contextMenu(m,{items:()=>{const n=M?.classList.contains("contents-collapsible"),y=M?.classList.contains("contents-collapsed"),g=d.find(([,l])=>l.classList.contains("is-active"));return[{header:"Contents"},...n?[{label:y?"Show the contents":"Collapse to a button",icon:y?"list":"minimize",action:()=>m.querySelector(".dm-contents-toggle")?.click()}]:[],{label:"Copy a link to this section",icon:"link",disabled:!g,action:async()=>{const l=`${location.origin}${location.pathname}#${g[0].id}`;try{await _.copyToClipboard(l),E.toast("Link copied.",{type:"success"})}catch{E.toast("Could not copy the link.",{type:"error"})}}},{label:"Back to the top",icon:"arrow-up",action:()=>window.scrollTo({top:0,behavior:"smooth"})}]}}),M?.classList.contains("contents-autohide")&&(m.addEventListener("pointerdown",n=>{n.pointerType!=="mouse"&&!m.classList.contains("is-revealed")&&(m.classList.add("is-revealed"),n.preventDefault())}),document.addEventListener("pointerdown",n=>{m.contains(n.target)||m.classList.remove("is-revealed")}))}
|
|
@@ -29,6 +29,9 @@
|
|
|
29
29
|
import {clip, plural} from '../../services/sidebarBadges.js';
|
|
30
30
|
import fs from 'fs/promises';
|
|
31
31
|
import path from 'path';
|
|
32
|
+
import {getConfig, saveConfig} from '../../config.js';
|
|
33
|
+
import {invalidateMeasurementId, measurementIdOf, normaliseMeasurementId} from '../../services/googleAnalytics.js';
|
|
34
|
+
import {invalidateTags} from '../../services/cache/index.js';
|
|
32
35
|
import {authenticate as defaultAuthenticate, requireAdmin as defaultRequireAdmin} from '../../middleware/auth.js';
|
|
33
36
|
|
|
34
37
|
/**
|
|
@@ -371,6 +374,22 @@ function shouldRecordJourney(dayEntries, cap = JOURNEY_DAILY_CAP) {
|
|
|
371
374
|
export async function analyticsRoutes(fastify, opts = {}) {
|
|
372
375
|
const authenticate = opts.authenticate || defaultAuthenticate;
|
|
373
376
|
const requireAdmin = opts.requireAdmin || defaultRequireAdmin;
|
|
377
|
+
fastify.get('/settings', {preHandler: [authenticate, requireAdmin]}, async () => {
|
|
378
|
+
const site = getConfig('site');
|
|
379
|
+
return {measurementId: measurementIdOf(site), cookieBannerEnabled: site.cookieConsent?.enabled !== false};
|
|
380
|
+
});
|
|
381
|
+
fastify.put('/settings', {preHandler: [authenticate, requireAdmin]}, async (request, reply) => {
|
|
382
|
+
const raw = request.body?.measurementId;
|
|
383
|
+
if (typeof raw !== 'string' || (raw.trim() && !normaliseMeasurementId(raw))) {
|
|
384
|
+
return reply.code(400).send({error: 'A Google Analytics measurement ID looks like G-XXXXXXXXXX.'});
|
|
385
|
+
}
|
|
386
|
+
const site = getConfig('site');
|
|
387
|
+
const measurementId = normaliseMeasurementId(raw);
|
|
388
|
+
saveConfig('site', {...site, googleAnalytics: {...site.googleAnalytics, measurementId}});
|
|
389
|
+
invalidateMeasurementId();
|
|
390
|
+
await invalidateTags(['site']);
|
|
391
|
+
return {measurementId, cookieBannerEnabled: site.cookieConsent?.enabled !== false};
|
|
392
|
+
});
|
|
374
393
|
|
|
375
394
|
await migrateLegacy();
|
|
376
395
|
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/** Site-wide Google Business Profile connection, managed through SEO. */
|
|
2
|
+
import {createHash} from 'node:crypto';
|
|
3
|
+
import {createGoogleBusiness} from '../../services/googleBusiness.js';
|
|
4
|
+
import {originOf, trustedOrigin} from '../../services/passwordReset.js';
|
|
5
|
+
import {getUserById} from '../../services/users.js';
|
|
6
|
+
import {getPermissionsFor} from '../../services/roles.js';
|
|
7
|
+
import {getEffectiveRoles} from '../../services/userRoles.js';
|
|
8
|
+
import {resolveViewer} from '../../services/viewerSession.js';
|
|
9
|
+
|
|
10
|
+
const CALLBACK = '/api/seo/google-business/callback';
|
|
11
|
+
const cookieName = state => `dm_business_${createHash('sha256').update(state).digest('hex').slice(0, 16)}`;
|
|
12
|
+
const cookieValue = (header, name) => String(header || '').split(';').map(s => s.trim()).find(s => s.startsWith(`${name}=`))?.slice(name.length + 1) || '';
|
|
13
|
+
const esc = value => String(value).replace(/&/g, '&').replace(/</g, '<').replace(/"/g, '"');
|
|
14
|
+
|
|
15
|
+
export async function googleBusinessRoutes(fastify, opts) {
|
|
16
|
+
const service = opts.service || createGoogleBusiness();
|
|
17
|
+
const mayManage = {preHandler: [opts.authenticate, opts.requirePermission('seo', 'manage')]};
|
|
18
|
+
const redirectFor = opts.redirectFor || (async request => {
|
|
19
|
+
const origin = await trustedOrigin(originOf(request));
|
|
20
|
+
if (!origin) throw Object.assign(new Error('Set the Site URL in Site Settings before connecting Google.'), {statusCode: 400});
|
|
21
|
+
return `${origin}${CALLBACK}`;
|
|
22
|
+
});
|
|
23
|
+
const mayComplete = opts.mayComplete || (async id => {
|
|
24
|
+
const user = await getUserById(id);
|
|
25
|
+
return user?.isActive === true && getEffectiveRoles(user).some(r => getPermissionsFor('seo', 'manage').includes(r));
|
|
26
|
+
});
|
|
27
|
+
fastify.addHook('onSend', async (request, reply, payload) => { reply.header('Cache-Control', 'no-store'); return payload; });
|
|
28
|
+
// Google token failures are not CMS session failures: avoid the admin client's 401 login retry.
|
|
29
|
+
const safe = handler => async (request, reply) => {
|
|
30
|
+
try { return await handler(request, reply); }
|
|
31
|
+
catch (e) { return reply.code(e.statusCode === 401 ? 409 : e.statusCode || 500).send({error: e.statusCode ? e.message : 'The Google connection could not be saved. Try again.'}); }
|
|
32
|
+
};
|
|
33
|
+
fastify.get('/status', mayManage, safe(async request => ({...service.status(), redirectUri: await redirectFor(request)})));
|
|
34
|
+
fastify.post('/connect', {...mayManage, config: {rateLimit: {max: 10, timeWindow: '1 minute'}}}, safe(async (request, reply) => {
|
|
35
|
+
if (!request.user?.id) return reply.code(401).send({error: 'Sign in before connecting Google.'});
|
|
36
|
+
const redirectUri = await redirectFor(request);
|
|
37
|
+
const {url, state, binding} = service.begin({...request.body, redirectUri, userId: request.user.id});
|
|
38
|
+
reply.header('Set-Cookie', `${cookieName(state)}=${binding}; Path=${CALLBACK}; HttpOnly; SameSite=Lax; Max-Age=600${redirectUri.startsWith('https:') ? '; Secure' : ''}`);
|
|
39
|
+
return {url};
|
|
40
|
+
}));
|
|
41
|
+
fastify.get('/callback', {config: {rateLimit: {max: 30, timeWindow: '1 minute'}}}, async (request, reply) => {
|
|
42
|
+
const state = String(request.query?.state || '').slice(0, 200);
|
|
43
|
+
const name = cookieName(state);
|
|
44
|
+
reply.header('Set-Cookie', `${name}=; Path=${CALLBACK}; HttpOnly; SameSite=Lax; Max-Age=0`);
|
|
45
|
+
let ok = false;
|
|
46
|
+
let message;
|
|
47
|
+
try {
|
|
48
|
+
const ticket = service.take(state, cookieValue(request.headers.cookie, name));
|
|
49
|
+
const viewer = await resolveViewer(request).catch(() => null);
|
|
50
|
+
if (viewer?.id && viewer.id !== ticket.userId) throw new Error('Google sign-in was started by someone else. Press Connect yourself.');
|
|
51
|
+
if (!(await mayComplete(ticket.userId))) throw new Error('You no longer have permission to connect Google Business Profile.');
|
|
52
|
+
if (request.query?.error) throw new Error('Google sign-in was cancelled or access was declined. Press Connect to try again.');
|
|
53
|
+
await service.complete(ticket, request.query?.code);
|
|
54
|
+
ok = true;
|
|
55
|
+
message = 'Google Business Profile connected. Return to SEO to choose a location.';
|
|
56
|
+
} catch (e) { message = e.statusCode || e.message?.startsWith('Google sign-in') || e.message?.startsWith('You no longer') ? e.message : 'The Google connection could not be completed. Press Connect to try again.'; }
|
|
57
|
+
return reply.type('text/html; charset=utf-8').header('Referrer-Policy', 'no-referrer').header('X-Robots-Tag', 'noindex')
|
|
58
|
+
.send(`<!doctype html><html lang="en"><meta charset="utf-8"><meta name="viewport" content="width=device-width"><title>Google Business Profile</title><body><main style="font-family:system-ui;max-width:36rem;margin:15vh auto;padding:1rem"><h1>${ok ? 'Google connected' : 'Google not connected'}</h1><p id="business-result" data-ok="${ok}">${esc(message)}</p><p>You can close this window and return to SEO.</p><a href="/admin/#/seo">Return to SEO</a></main><script src="/admin/js/lib/google-business-callback.js" defer></script></body></html>`);
|
|
59
|
+
});
|
|
60
|
+
fastify.get('/accounts', mayManage, safe(request => service.accounts(request.query?.pageToken)));
|
|
61
|
+
fastify.get('/locations', mayManage, safe(request => service.locations(request.query?.account, request.query?.pageToken)));
|
|
62
|
+
fastify.put('/location', mayManage, safe(request => service.select(request.body?.name)));
|
|
63
|
+
fastify.delete('/connection', mayManage, safe(() => service.disconnect()));
|
|
64
|
+
}
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* GET /api/menus/_count - the sidebar badge (the underscore can never be a slug)
|
|
6
6
|
* GET /api/menus/:slug - full menu
|
|
7
7
|
* POST /api/menus - create
|
|
8
|
-
* PUT /api/menus/:slug - replace
|
|
8
|
+
* PUT /api/menus/:slug - replace (409 if `revision` is given and the menu has changed since)
|
|
9
9
|
* DELETE /api/menus/:slug - delete (409 if mapped to a slot)
|
|
10
10
|
* POST /api/menus/:slug/duplicate - copy as <slug>-copy
|
|
11
11
|
*
|
|
@@ -26,6 +26,7 @@ import {
|
|
|
26
26
|
getLocations,
|
|
27
27
|
getMenu,
|
|
28
28
|
listMenus,
|
|
29
|
+
menuRevision,
|
|
29
30
|
updateMenu
|
|
30
31
|
} from '../../services/menus.js';
|
|
31
32
|
import * as cache from '../../services/cache/index.js';
|
|
@@ -110,7 +111,8 @@ export async function menusRoutes(fastify, opts = {}) {
|
|
|
110
111
|
if (!canSeeArtefact(request.user, menu)) {
|
|
111
112
|
return reply.status(403).send({error: 'Access denied for this project'});
|
|
112
113
|
}
|
|
113
|
-
|
|
114
|
+
// The fingerprint the editor sends back on save (see menuRevision).
|
|
115
|
+
return {...menu, revision: menuRevision(menu)};
|
|
114
116
|
});
|
|
115
117
|
|
|
116
118
|
fastify.post('/menus', canCreate, async (request, reply) => {
|
|
@@ -126,10 +128,12 @@ export async function menusRoutes(fastify, opts = {}) {
|
|
|
126
128
|
|
|
127
129
|
fastify.put('/menus/:slug', canUpdate, async (request, reply) => {
|
|
128
130
|
try {
|
|
129
|
-
const
|
|
131
|
+
const {revision, ...body} = request.body || {};
|
|
132
|
+
const menu = await updateMenu(request.params.slug, body, {baseRevision: revision});
|
|
130
133
|
await cache.invalidateTags([`menu:${menu.slug}`, 'nav']);
|
|
131
|
-
return menu;
|
|
134
|
+
return {...menu, revision: menuRevision(menu)};
|
|
132
135
|
} catch (err) {
|
|
136
|
+
if (err.code === 'MENU_STALE') return reply.status(409).send({error: err.message, stale: true});
|
|
133
137
|
const status = /not found/.test(err.message) ? 404 : 400;
|
|
134
138
|
return reply.status(status).send({error: err.message});
|
|
135
139
|
}
|
package/server/routes/api/seo.js
CHANGED
|
@@ -9,12 +9,14 @@
|
|
|
9
9
|
* Was /api/plugins/seo while SEO was a plugin (0.89.0).
|
|
10
10
|
*/
|
|
11
11
|
import {authenticate as defaultAuthenticate, requirePermission as defaultRequirePermission} from '../../middleware/auth.js';
|
|
12
|
-
import {getConfig} from '../../config.js';
|
|
12
|
+
import {getConfig, saveConfig} from '../../config.js';
|
|
13
13
|
import {getPage, updatePage} from '../../services/content.js';
|
|
14
14
|
import {getSitemapSources} from '../../services/hooks.js';
|
|
15
15
|
import {seoEngine} from '../../services/seo/index.js';
|
|
16
16
|
import {invalidateTags} from '../../services/cache/index.js';
|
|
17
17
|
import {excluded} from '../../../admin/js/lib/seo-settings.js';
|
|
18
|
+
import {checkSeoDefaults, seoDefaults} from '../../../admin/js/lib/seo-defaults.js';
|
|
19
|
+
import {googleBusinessRoutes} from './google-business.js';
|
|
18
20
|
|
|
19
21
|
/**
|
|
20
22
|
* @param {import('fastify').FastifyInstance} fastify
|
|
@@ -194,11 +196,20 @@ export async function seoRoutes(fastify, opts = {}) {
|
|
|
194
196
|
robots: buildRobotsTxt(origin)};
|
|
195
197
|
});
|
|
196
198
|
|
|
197
|
-
fastify.
|
|
199
|
+
await fastify.register(googleBusinessRoutes, {prefix: '/google-business', authenticate, requirePermission, ...opts.googleBusiness});
|
|
200
|
+
|
|
201
|
+
fastify.get('/settings', may('read'), async () => ({settings: seo().settings(), defaults: seoDefaults(site()), hooks: true}));
|
|
198
202
|
fastify.put('/settings', may('manage'), async (request, reply) => {
|
|
203
|
+
const defaults = request.body?.siteDefaults === undefined ? null : checkSeoDefaults(request.body.siteDefaults);
|
|
204
|
+
if (defaults?.error) return reply.code(400).send({error: defaults.error});
|
|
199
205
|
const r = seo().saveSettings(request.body ?? {});
|
|
200
206
|
if (r.errors) return reply.code(400).send({error: Object.values(r.errors).join(' '), errors: r.errors});
|
|
201
|
-
|
|
207
|
+
if (defaults) {
|
|
208
|
+
const current = site();
|
|
209
|
+
saveConfig('site', {...current, seo: {...current.seo, ...defaults.defaults}});
|
|
210
|
+
await invalidateTags(['site']);
|
|
211
|
+
}
|
|
212
|
+
return {settings: r.settings, defaults: seoDefaults(site()), hooks: true};
|
|
202
213
|
});
|
|
203
214
|
|
|
204
215
|
fastify.get('/extensions', may('read'), async () => ({
|
|
@@ -24,6 +24,7 @@ export function adminBrandFor(site = {}) {
|
|
|
24
24
|
const str = (v) => (typeof v === 'string' ? v.replace(/[ \t\n\f\r]+/g, ' ').trim() : '');
|
|
25
25
|
const logo = str(own.logo);
|
|
26
26
|
return {
|
|
27
|
+
enabled: own.enabled !== false,
|
|
27
28
|
title: (str(own.title) || str(site?.title) || DEFAULT_ADMIN_TITLE).slice(0, 120),
|
|
28
29
|
// Same rule as the sidebar header: a site path or an http(s) address, nothing else.
|
|
29
30
|
logo: /^(?:\/(?!\/)|https?:\/\/)/i.test(logo) ? logo : '',
|