domma-cms 1.6.5 → 1.6.7
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/README.md +1 -1
- package/admin/js/lib/tool-kit.js +1 -1
- package/admin/js/lib/tool-recovery.js +12 -0
- package/admin/js/templates/analytics.html +5 -0
- package/admin/js/templates/contacts.html +4 -0
- package/admin/js/templates/docs/usage-tools.html +12 -0
- package/admin/js/templates/notes.html +4 -0
- package/admin/js/templates/todo.html +4 -0
- package/admin/js/views/analytics.js +7 -7
- package/admin/js/views/contacts.js +22 -22
- package/admin/js/views/notes.js +8 -8
- package/admin/js/views/todo.js +9 -9
- package/config/menus/admin-sidebar.json +1 -1
- package/package.json +1 -1
- package/public/js/collection-context.js +2 -2
- package/public/js/context-menus.js +1 -1
- package/public/js/effects.js +1 -1
- package/public/js/site.js +1 -1
- package/public/js/utf8-base64.mjs +1 -0
- package/server/routes/api/contacts.js +12 -14
- package/server/routes/api/notes.js +7 -6
- package/server/routes/api/todo.js +7 -5
- package/server/services/collections.js +11 -2
- package/server/services/markdown.js +10 -10
- package/server/services/renderer.js +2 -2
- package/server/services/sidebar-migration.js +15 -4
- package/server/services/tool-trash.js +39 -0
- package/server/templates/page.html +4 -4
package/public/js/site.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{makeBadgeEl as O,applyColour as B,applyPill as X,applyFloat as G,applyHighlight as z,applyItemHighlight as Y,itemHighlightAttrs as U,isCurrentItem as j,menuAlign as K,applyClasses as I,cssClasses as R}from"/public/js/menu-decor.mjs?v=20261009-hover-darken";import{initFloatingMenus as Q}from"/public/js/menu-float.js?v=20260829-float-panel";import{enhancePickerFields as P,markInvalidFields as V,showValidationPopup as J}from"/public/js/forms.js?v=20261009-form-classes";import{buildWizardSteps as Z}from"/public/js/form-steps.mjs";import{applyFormClasses as W,applySubmitButtons as ee,applyWizardButtons as te}from"/public/js/form-submit.mjs";$(()=>{const x=window.__CMS_NAV__||{},f=window.__CMS_SITE__||{};if(f.autoTheme?.enabled){let t=function(e){const i=(e||"07:00").split(":");return+i[0]*60+(+i[1]||0)},s=function(){const e=new Date,i=e.getHours()*60+e.getMinutes();return i>=t(n.dayStart)&&i<t(n.nightStart)?n.dayTheme:n.nightTheme},a=function(e){const i=n.apply&&n.apply[e]||{theme:e,custom:""};Domma.theme.set(i.theme);for(const p of[...document.body.classList])p.startsWith("dm-custom-")&&document.body.classList.remove(p);i.custom&&document.body.classList.add(i.custom)};const n=f.autoTheme;a(s()),setInterval(()=>a(s()),6e4)}const _=$("#site-navbar");if(_.length&&x.brand){let s=function(c,g=0){return Array.isArray(c)?c.filter(r=>!(r&&r.type==="spacer")).filter(r=>!(r&&r.type==="separator"&&g===0)).map(r=>r&&r.type==="separator"?{divider:!0}:Array.isArray(r?.items)?{...r,items:s(r.items,g+1)}:r):c},a=function(c){return Array.isArray(c)?c.filter(g=>!(g&&(g.type==="separator"||g.type==="spacer"))):[]};const n={...x.brand},t=n.size&&n.size!=="md"?` navbar-brand-${n.size}`:"";if(n.logo||n.icon||n.tagline){let c="";n.logo?c+=`<img src="${n.logo}" class="navbar-brand-logo" alt="${n.text||""}">`:n.icon&&(c+=`<span data-icon="${n.icon}" style="width:1.1em;height:1.1em;margin-right:.35em;vertical-align:middle;"></span>`),n.text&&(c+=`<span class="navbar-brand-text${t}">${n.text}</span>`),n.tagline&&(c+=`<small class="navbar-brand-tagline">${n.tagline}</small>`),n.html=c}else t&&n.text&&(n.html=`<span class="navbar-brand-text${t}">${n.text}</span>`);const e=x.position==="floating",i=x.orientation==="vertical";Domma.elements.navbar("#site-navbar",{brand:n,items:s(x.items||[]),variant:x.variant||"dark",position:e?"static":x.position||"sticky",collapsible:!0,appearOnHover:!!x.appearOnHover}),Domma.icons.scan("#site-navbar");const p=_.get(0);p.classList.add(`dm-nav-align-${K(x)||"centre"}`),i&&_.get(0).classList.add("dm-nav-vertical",x.side==="right"?"dm-nav-vertical-right":"dm-nav-vertical-left"),z(p,x),I(p,x.class),e&&(G(_.get(0),x),Q(_.get(0).parentNode||document));const y=p.querySelector(".navbar-toggle"),u=p.querySelector(".navbar-collapse");if(y&&u){let c=null;const g=()=>{u.classList.contains("show")&&y.click()},r=()=>{c&&(c.remove(),c=null),document.body.classList.remove("dm-drawer-open")},o=()=>{c||(c=document.createElement("div"),c.className="dm-navbar-backdrop",c.addEventListener("click",g),document.body.appendChild(c),document.body.classList.add("dm-drawer-open"))};y.addEventListener("click",()=>{requestAnimationFrame(()=>{u.classList.contains("show")?o():r()})}),u.addEventListener("click",d=>{d.target.closest("a")&&requestAnimationFrame(r)}),document.addEventListener("keydown",d=>{d.key==="Escape"&&u.classList.contains("show")&&g()})}const m=p.querySelector(".navbar-nav");if(m){const c=Array.from(m.children);let g=0;for(const r of x.items||[])if(!(r&&r.type==="separator")){if(r&&r.type==="spacer"){const o=document.createElement("li"),d=["xs","sm","md","lg","xl","flex"].includes(r.size)?r.size:"md";o.className=`dm-menu-spacer dm-menu-spacer--${d}`,o.setAttribute("aria-hidden","true"),m.insertBefore(o,c[g]||null);continue}g++}}const b=a(x.items||[]);p.querySelectorAll(".navbar-link[data-index], .navbar-dropdown-item[data-index]").forEach(c=>{const g=b[parseInt(c.dataset.index,10)];if(!g)return;const r=c.dataset.subindex!=null,o=r&&Array.isArray(g.items)?g.items[parseInt(c.dataset.subindex,10)]:g;if(!o)return;o.colour&&B(c,o.colour),o.pill&&!r&&X(c,o.pill),Y(c,o),I(c.closest("li"),o.class);const d=O(o.badge);d&&c.appendChild(d),j(o.url,location.pathname)&&(c.classList.add("active"),c.setAttribute("aria-current","page"))})}const C=$("#site-footer");if(C.length){let e=function(o){return String(o??"").replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""")},i=function(o){return e(o).replace(/'/g,"'")},p=function(o){if(o&&o.type==="separator")return'<li class="footer-sep"><hr></li>';if(o&&o.type==="spacer")return`<li class="dm-menu-spacer dm-menu-spacer--${["xs","sm","md","lg","xl","flex"].includes(o.size)?o.size:"md"}" aria-hidden="true"></li>`;const d=i(o.url||"#"),l=e(o.text||""),w=Array.isArray(o.items)&&o.items.length?`<ul>${o.items.map(p).join("")}</ul>`:"",v=U(o),D=[v.hover&&` data-menu-item-hover="${i(v.hover)}"`,v.active&&` data-menu-item-active="${i(v.active)}"`].filter(Boolean).join(""),M=v.accent?` style="--dm-menu-highlight:${i(v.accent)}"`:"",k=R(o.class).join(" ");return`<li${k?` class="${i(k)}"`:""}><a href="${d}"${D}${M}>${l}</a>${w}</li>`},y=function(o,d){return(o||[]).forEach(l=>{l&&(l.type==="separator"||l.type==="spacer")||(d.push(l),Array.isArray(l.items)&&l.items.length&&y(l.items,d))}),d};const n=f.social||{},t={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 s='<div class="footer-inner container">';const a=window.__CMS_FOOTER__||{primary:[],legal:[],copyright:""};if(a.copyright&&(s+=`<p>${e(a.copyright)}</p>`),a.primary.length&&(s+=`<nav class="footer-nav footer-nav--primary"><ul>${a.primary.map(p).join("")}</ul></nav>`),a.legal.length&&(s+=`<nav class="footer-nav footer-nav--legal"><ul>${a.legal.map(p).join("")}</ul></nav>`),f.footer){const o=Object.keys(t).filter(l=>n[l]),d=R(f.footer.socialClass).join(" ");o.length&&(s+='<div class="footer-social">',o.forEach(l=>{const{label:w,svg:v}=t[l];s+=`<a href="${n[l]}" target="_blank" rel="noopener noreferrer" aria-label="${w}" class="footer-social-link${d?` ${i(d)}`:""}">${v}</a>`}),s+="</div>")}s+="</div>",C.html(s),I(C.get(0),f.footer?.class);const u=[...y(a.primary,[]),...y(a.legal,[])];C.get(0).querySelectorAll(".footer-nav a").forEach((o,d)=>{const l=u[d];if(!l)return;l.colour&&B(o,l.colour);const w=O(l.badge);w&&o.appendChild(w),j(l.url,location.pathname)&&(o.classList.add("is-active"),o.setAttribute("aria-current","page"))}),z(C.get(0).querySelector(".footer-nav--primary"),{highlight:a.primaryHighlight}),z(C.get(0).querySelector(".footer-nav--legal"),{highlight:a.legalHighlight}),I(C.get(0).querySelector(".footer-nav--primary"),a.primaryClass),I(C.get(0).querySelector(".footer-nav--legal"),a.legalClass);const b=f.footer?.showMotionToggle!==!1,c=S.get("reduced_motion"),g=c!==null?!!c:!!(window.matchMedia&&window.matchMedia("(prefers-reduced-motion: reduce)").matches),r=C.get(0).querySelector(".footer-inner");if(b&&r){const o=document.createElement("input");o.type="checkbox",o.className="form-switch-input",o.id="dm-motion-switch",o.checked=g,o.addEventListener("change",function(){S.set("reduced_motion",this.checked),window.location.reload()});const d=document.createElement("span");d.className="form-switch-label",d.textContent="Reduce motion";const l=document.createElement("label");l.className="form-switch footer-motion-switch",l.title="Reduce motion",l.appendChild(o),l.appendChild(d),r.appendChild(l)}}$("#site-sidebar").length&&Domma.elements.sidebar("#site-sidebar",{autoGenerate:!0,selector:"h2, h3",collapsible:!1,push:!0,contentSelector:".site-content"}),Domma.icons.scan(),Domma.syntax?.scan&&Domma.syntax.scan();const h=$(".page-body");if(h.length){h.find(".accordion").each(function(){Domma.elements.accordion(this,{allowMultiple:this.dataset.multi==="true"})}),h.find(".tabs").each(function(){Domma.elements.tabs(this)}),h.find(".carousel").each(function(){const n={autoplay:this.dataset.autoplay==="true",interval:parseInt(this.dataset.interval,10)||5e3,loop:this.dataset.loop!=="false",animation:this.dataset.animation||"slide"};if(this.dataset.animationDuration){const t=parseInt(this.dataset.animationDuration,10);Number.isFinite(t)&&(n.animationDuration=t)}this.dataset.animationEasing&&(n.animationEasing=this.dataset.animationEasing),this.dataset.ctx&&(this._dmCtxSlides=Array.from(this.querySelectorAll(".carousel-slide")).map(t=>t.cloneNode(!0)),this._dmCarouselOpts=n),this._dmCarousel=Domma.elements.carousel(this,n)}),h.find(".dm-countdown").each(function(){const n={autoStart:!0},t=String(this.dataset.format||"").trim(),s=String(this.dataset.to||"").trim(),a=/^(\d{4})-(\d{2})-(\d{2})$/.exec(s),e=a?new Date(+a[1],+a[2]-1,+a[3]).getTime():s?new Date(s).getTime():NaN;Number.isFinite(e)?n.duration=Math.max(0,e-Date.now()):this.dataset.duration&&(n.duration=(parseInt(this.dataset.duration,10)||0)*1e3);const i=/^dd:/i.test(t);n.format=i?"hh:mm:ss":t||(n.duration>=36e5?"hh:mm:ss":"mm:ss");const p=this,y=m=>String(m).padStart(2,"0"),u=m=>{const b=Math.max(0,Math.floor(m/1e3)),c=p.querySelector(".dm-timer-time");c&&(c.textContent=`${y(Math.floor(b/86400))}:${y(Math.floor(b%86400/3600))}:${y(Math.floor(b%3600/60))}:${y(b%60)}`)};i&&(n.onTick=u),Domma.elements.timer(this,n),i&&u(n.duration||0)}),h.find("[data-tooltip]").each(function(){Domma.elements.tooltip(this,{content:$(this).data("tooltip"),position:$(this).data("tooltip-position")||"top"})}),h.find(".dm-progression").each(function(){const n=Array.from(this.querySelectorAll(".dm-progression-item")).map(a=>{const e={title:a.querySelector(".dm-progression-item-title")?.textContent?.trim()||"",description:a.querySelector(".dm-progression-item-body")?.innerHTML?.trim()||""};return a.dataset.date&&(e.date=a.dataset.date),a.dataset.status&&(e.status=a.dataset.status),a.dataset.entryId&&(e._entryId=a.dataset.entryId),e}),t=this.dataset.layout==="centred"?"centered":this.dataset.layout||"vertical",s=Domma.elements.progression(this,{layout:t,theme:this.dataset.theme||"minimal",mode:this.dataset.mode||"timeline",statusIcons:!0,allowHtml:!0,...n.length?{items:n}:{}});n.length&&(this._dmCtx={instance:s,items:n})});try{Domma.effects.reveal(".page-body .hero",{animation:"slide-up",duration:480,threshold:.06,stagger:60,once:!1})}catch{}document.querySelectorAll(".page-body .row[data-reveal]").forEach(n=>{const t=n.dataset.revealMode||"stagger",s=n.dataset.revealAnimation||"slide-up",a=parseInt(n.dataset.revealDuration,10)||400,e=parseInt(n.dataset.revealStagger,10)||60,i=parseInt(n.dataset.revealDelay,10)||0,p=n.dataset.revealDirection==="rtl",y=Array.from(n.children),u={"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)"};y.forEach((m,b)=>{m.style.opacity="0",m.style.transform=u[s]||"",m.style.transition=`opacity ${a}ms ease, transform ${a}ms ease`;const c=p?y.length-1-b:b;m.style.transitionDelay=t==="stagger"?`${i+c*e}ms`:`${i}ms`}),requestAnimationFrame(()=>requestAnimationFrame(()=>{const m=new IntersectionObserver(b=>{b.forEach(c=>{c.isIntersecting&&(c.target.offsetWidth,c.target.style.opacity="1",c.target.style.transform="none",m.unobserve(c.target))})},{threshold:.1});y.forEach(b=>m.observe(b))}))}),h.find(".card[data-collapsible]").each(function(){const n=this.querySelector(".card-header");n&&n.addEventListener("click",()=>this.classList.toggle("is-collapsed"))}),h.find(".dm-so-trigger").each(function(){this.addEventListener("click",()=>{const n=this.dataset.soTarget,t=document.getElementById(n);if(!t)return;const s=E.slideover({title:t.dataset.soTitle||"",size:t.dataset.soSize||"md",position:t.dataset.soPosition||"right"});t.style.display="",s.setContent(t),s.open()})})}if(typeof $.setup=="function"){const n=Object.assign({},window.__CMS_DCONFIG__||{});if(document.querySelectorAll(".dm-page-config[data-config]").forEach(t=>{try{const s=atob(t.dataset.config),a=JSON.parse(s);Object.assign(n,a)}catch{}}),Object.keys(n).length>0){const t={};for(const[s,a]of Object.entries(n)){const e=a?.events?.click,{confirm:i,toast:p,alert:y,prompt:u,...m}=e||{};i||p||y||u?($(s).on("click",async function(c){if(c.preventDefault(),i&&!await E.confirm(i))return;let g=null;if(!(u&&(g=await E.prompt(u,{inputPlaceholder:m.promptPlaceholder||"",inputValue:m.promptDefault||""}),g===null))){if(m.target){const r=$(m.target);m.toggleClass&&r.toggleClass(m.toggleClass),m.addClass&&r.addClass(m.addClass),m.removeClass&&r.removeClass(m.removeClass),g!==null&&(m.setText&&r.text(g),m.setVal&&r.val(g),m.setAttr&&r.attr(m.setAttr,g))}m.href&&(window.location.href=m.href),p&&E.toast(p,{type:m.toastType||"success"}),y&&E.alert(y)}}),Object.keys(m).length&&(t[s]={...a,events:{...a.events,click:m}})):t[s]=a}$.setup(t)}}h.length&&q(h.get(0))}),window.wireCTAButtons=q;function q(x){x.querySelectorAll(".dm-cta-trigger").forEach(f=>{f.__ctaWired||(f.__ctaWired=!0,f.addEventListener("click",async()=>{const _=f.dataset.action,C=f.dataset.entry,A=f.dataset.confirm;let h=S.get("auth_token");if(!h){E.toast("Please log in to perform this action.",{type:"warning"});return}if(A&&!await E.confirm(A))return;const n=Array.from(f.childNodes).map(s=>s.cloneNode(!0));f.disabled=!0,f.textContent="Running\u2026";const t=s=>fetch(`/api/actions/${_}/public`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${s}`},body:JSON.stringify(f.dataset.scope?{entryId:C,scope:f.dataset.scope}:{entryId:C})});try{let s=await t(h);if(s.status===401){const e=S.get("auth_refresh_token");if(e){const i=await fetch("/api/auth/refresh",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({refreshToken:e})});if(i.ok){const{token:p}=await i.json();S.set("auth_token",p),h=p,s=await t(h)}}}const a=await s.json().catch(()=>({}));if(!s.ok)throw new Error(a.error||a.message||`Error ${s.status}`);E.toast(a.message||"Action completed.",{type:"success"})}catch(s){E.toast(s.message||"Action failed.",{type:"error"})}finally{f.disabled=!1,f.textContent="",n.forEach(s=>f.appendChild(s)),Domma.icons.scan(f)}}))})}(function(){const f=document.querySelectorAll('.dm-collection-hydrate[data-collection-scope="mine"]');if(!f.length)return;const _=S.get("auth_token");function C(h,n){const t=document.createElement("div");t.className="dm-collection-display dm-collection-empty";const s=document.createElement("p");if(n){s.appendChild(document.createTextNode("Please "));const a=document.createElement("a");a.href="/login",a.textContent="sign in",s.appendChild(a),s.appendChild(document.createTextNode(" to view your entries."))}else s.textContent=h;return t.appendChild(s),t}function A(h,n){const t=document.createElement("template");t.innerHTML=n||"",h.replaceWith(t.content)}f.forEach(async h=>{const n=h.dataset.collectionAttrs;if(!n)return;if(!_){h.replaceChildren(C("",!0));return}const t=s=>fetch("/api/collections/render-scope",{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${s}`},body:JSON.stringify({attrs:n})});try{let s=_,a=await t(s);if(a.status===401){const i=S.get("auth_refresh_token");if(i){const p=await fetch("/api/auth/refresh",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({refreshToken:i})});if(p.ok){const{token:y}=await p.json();S.set("auth_token",y),s=y,a=await t(s)}}}if(!a.ok){const{error:i}=await a.json().catch(()=>({}));h.replaceChildren(C(i||"Unable to load entries.",!1));return}const{html:e}=await a.json();A(h,e),window.DommaCollectionBrowser?.hydrate&&window.DommaCollectionBrowser.hydrate(document)}catch{h.replaceChildren(C("Unable to load entries.",!1))}}),setTimeout(()=>{typeof Domma<"u"&&Domma.icons?.scan&&Domma.icons.scan(),typeof Domma<"u"&&Domma.syntax?.scan&&Domma.syntax.scan(),q(document.body)},100)})(),(function(){const f=document.querySelectorAll("[data-collection-table]");!f.length||typeof T>"u"||!T.create||f.forEach(_=>{let C;try{C=JSON.parse(atob(_.dataset.payload))}catch{return}const{columns:A,rows:h,search:n,sortable:t,exportable:s,pageSize:a,empty:e,ctaConfig:i,slug:p,exportCfg:y}=C;if(!A?.length)return;if(i){const b=A.findIndex(c=>c.key==="_cta");b!==-1&&(A[b]={key:"_cta",title:"",render:(c,g)=>{const r=document.createElement("button");if(r.className=`btn btn-${i.style||"primary"} dm-cta-trigger`,r.dataset.action=i.action||"",i.scope&&(r.dataset.scope=i.scope),r.dataset.entry=g._entryId||"",i.confirm&&(r.dataset.confirm=i.confirm),i.icon){const o=document.createElement("span");o.dataset.icon=i.icon,r.appendChild(o),r.appendChild(document.createTextNode(" "))}return r.appendChild(document.createTextNode(i.label||"Run")),r}})}const u="col-table-"+Math.random().toString(36).slice(2,7),m=document.createElement("div");m.id=u,_.replaceChildren(m),T.create("#"+u,{data:h,columns:A,search:n,sortable:t,exportable:s,pageSize:a,emptyMessage:e}),i&&q(m),_._dmTable={columns:A,rows:h,slug:p,exportCfg:y,render(b){T.create("#"+u,{data:b,columns:A,search:n,sortable:t,exportable:s,pageSize:a,emptyMessage:e}),i&&q(m)}}})})(),(function(){const f=document.querySelectorAll("[data-form-inline]");if(!f.length||typeof F>"u")return;function _(t){const s=t&&t.logic;return!!(s&&(s.visibility||s.requirement))}function C(t,s){const a={};return(t||[]).forEach(e=>{if(e.type==="page-break"||e.type==="spacer"||!e.name)return;const i=e.type==="chooser"||!!(e.variant&&["select","radio","checkbox-group"].includes(e.type)),p=e.type==="checkbox"?"boolean":e.type==="date"||e.type==="icon"||e.type==="image"?"string":i?"chooser":e.type,y={...e.formConfig||{}};y.span==="full"&&s&&(y.span=s);const u={type:p,required:_(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},...y}};e.minLength!==void 0&&(u.minLength=e.minLength),e.maxLength!==void 0&&(u.maxLength=e.maxLength),e.min!==void 0&&(u.min=e.min),e.max!==void 0&&(u.max=e.max),i&&(e.variant&&(u.variant=e.variant),(e.multiple||e.type==="checkbox-group")&&(u.multiple=!0),e.density&&(u.density=e.density),e.columns&&(u.columns=e.columns),e.accent&&(u.accent=e.accent),e.accentStyle&&(u.accentStyle=e.accentStyle),e.glow&&(u.glow=!0),e.glowColour&&(u.glowColour=e.glowColour),e.shadow&&(u.shadow=e.shadow),e.shadowColour&&(u.shadowColour=e.shadowColour)),a[e.name]=u}),a}function A(t){const s={};return(t||[]).forEach(a=>{if(!(!a.name||a.type==="page-break"||a.type==="spacer")&&(a.type==="select"||a.type==="multiselect")&&a.required){const e=(a.options||[])[0];e&&(s[a.name]=typeof e=="object"?e.value:e)}}),s}function h(t,s){(s||[]).forEach(a=>{if(a.type!=="date"||!a.name)return;const e=t.querySelector(`[name="${a.name}"]`);e&&e.type!=="date"&&(e.type="date")})}function n(t,s,a){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=a?"color:var(--dm-danger);margin-top:.75rem;":"color:var(--dm-success);margin-top:.75rem;"}f.forEach(t=>{let s;try{s=JSON.parse(atob(t.dataset.formInline))}catch{return}const a=s.fields||[],e=s.settings||{},i=Number(e.columns)===3?3:2,p=e.layout||"grid",y=a.some(r=>r.type==="page-break"),u=async r=>{if(t.dataset.triggerBlocked==="true"){const o=t._formLogicRuntime,d=o&&o.submitBlock?o.submitBlock().reasons:[];n(t,d.join(" ")||"This form cannot be submitted yet.",!0);const l=new Error("Blocked by a form trigger");throw l.formSubmitFailed=!0,l}try{const o=t.querySelector(".fb-form-honeypot"),d=o?.querySelector('input[type="text"]')||null,l=o?.querySelector('[name="_t"]')||null,w=Object.assign({},r);delete w._t,o?.dataset.hpName&&delete w[o.dataset.hpName],d!==null&&(w._hp=d.value),l!==null&&(w._t=l.value);const v=await H.post(`/api/forms/submit/${s.slug}`,w);if(v&&v.error){n(t,v.error,!0);const D=new Error(v.error);throw D.formSubmitFailed=!0,D}if(v?.redirect){window.location.href=v.redirect;return}for(;t.firstChild;)t.removeChild(t.firstChild);n(t,v?.message||e.successMessage||"Thank you for your submission.",!1)}catch(o){throw o.formSubmitFailed||n(t,o.message||"Submission failed. Please try again.",!0),o}},m=Date.now();function b(r){const o=r.querySelector("form");if(!o||o.querySelector(".fb-form-honeypot"))return;const d=document.createElement("div");d.className="fb-form-honeypot",d.setAttribute("aria-hidden","true");const l=document.createElement("input");l.name=o.querySelector('[name="website"]')?"_website":"website",d.dataset.hpName=l.name,l.type="text",l.tabIndex=-1,l.autocomplete="url",l.placeholder="https://",d.appendChild(l);const w=document.createElement("input");w.name="_t",w.type="hidden",w.value=m,d.appendChild(w),o.appendChild(d)}function c(r){if(!window.FormLogicEngine||!a.some(d=>d.logic||Array.isArray(d.triggers)&&d.triggers.length))return null;const o=new window.FormLogicEngine.FormLogicRuntime(s,t,r?{baseValues:r}:void 0);if(o.init(),t._formLogicRuntime=o,t._formLogicObserved)return o;if(t._formLogicObserved=!0,t.parentNode&&typeof MutationObserver<"u"){const d=new MutationObserver(function(l){for(const w of l)for(const v of w.removedNodes)if(v===t||v.nodeType===1&&v.contains&&v.contains(t)){t._formLogicRuntime&&t._formLogicRuntime.destroy(),d.disconnect();return}});d.observe(t.parentNode,{childList:!0,subtree:!1}),t._formLogicObserver=d}return o}let g=!1;if(t.addEventListener("dm-form-end",r=>{const o=r.detail?.validation;if(o&&!o.valid){V(t,o.missing),J(o);return}g||(g=!0,u(r.detail?.values||{}).catch(()=>{}).finally(()=>{g=!1}))}),y&&F.wizard){const r=Z(a,s.title),o=r.map(k=>({title:k.title,description:k.description,fields:C(k.fields,i)})),d=r.map(k=>k.fields);let l=0,w=null,v=null;const D=k=>{const L=window.FormLogicEngine;if(!L||!L.validateFields)return;const N=L.validateFields(t,d[l]||[]);N.valid||(k.preventDefault(),k.stopImmediatePropagation(),V(t,N.missing),J(N))},M=()=>{const k=d[l]||a;te(t,e),W(t,e,k),h(t,k),P(t,k),e.honeypot!==!1&&b(t),w&&w.destroy(),w=c(v?.getStepData?v.getStepData():void 0),t.querySelectorAll(".wizard-next, .wizard-submit").forEach(L=>L.addEventListener("click",D,!0))};v=F.wizard(t,{schema:{steps:o},layout:p,columns:i,finishText:e.submitText||"Finish",onSubmit:u,onStepChange:k=>{l=k,setTimeout(M,0)}}),t.addEventListener("dm-form-trigger",k=>{if(k.detail?.type!=="goto-step"||!v?.goToStep)return;const L=Number(k.detail.action.step);!Number.isFinite(L)||L<0||L>=o.length||L!==l&&v.goToStep(L)}),Promise.resolve(v).then(M)}else if(F.render){const r=F.render(t,C(a,i),A(a),{submitText:e.submitText||"Submit",layout:p,columns:i,onSubmit:u});Promise.resolve(r).then(function(){ee(t,e,p),W(t,e,a),h(t,a),P(t,a),e.honeypot!==!1&&b(t),c()})}}),$(document).on("click",".dm-banner__dismiss",function(){$(this).closest(".dm-banner").remove()})})();
|
|
1
|
+
import{decodeBase64Json as N}from"/public/js/utf8-base64.mjs";import{makeBadgeEl as B,applyColour as j,applyPill as G,applyFloat as Y,applyHighlight as O,applyItemHighlight as U,itemHighlightAttrs as K,isCurrentItem as R,menuAlign as Q,applyClasses as I,cssClasses as P}from"/public/js/menu-decor.mjs?v=20261009-hover-darken";import{initFloatingMenus as Z}from"/public/js/menu-float.js?v=20260829-float-panel";import{enhancePickerFields as V,markInvalidFields as W,showValidationPopup as J}from"/public/js/forms.js?v=20261009-form-classes";import{buildWizardSteps as ee}from"/public/js/form-steps.mjs";import{applyFormClasses as X,applySubmitButtons as te,applyWizardButtons as ae}from"/public/js/form-submit.mjs";$(()=>{const x=window.__CMS_NAV__||{},f=window.__CMS_SITE__||{};if(f.autoTheme?.enabled){let t=function(e){const i=(e||"07:00").split(":");return+i[0]*60+(+i[1]||0)},s=function(){const e=new Date,i=e.getHours()*60+e.getMinutes();return i>=t(n.dayStart)&&i<t(n.nightStart)?n.dayTheme:n.nightTheme},o=function(e){const i=n.apply&&n.apply[e]||{theme:e,custom:""};Domma.theme.set(i.theme);for(const p of[...document.body.classList])p.startsWith("dm-custom-")&&document.body.classList.remove(p);i.custom&&document.body.classList.add(i.custom)};const n=f.autoTheme;o(s()),setInterval(()=>o(s()),6e4)}const _=$("#site-navbar");if(_.length&&x.brand){let s=function(c,g=0){return Array.isArray(c)?c.filter(r=>!(r&&r.type==="spacer")).filter(r=>!(r&&r.type==="separator"&&g===0)).map(r=>r&&r.type==="separator"?{divider:!0}:Array.isArray(r?.items)?{...r,items:s(r.items,g+1)}:r):c},o=function(c){return Array.isArray(c)?c.filter(g=>!(g&&(g.type==="separator"||g.type==="spacer"))):[]};const n={...x.brand},t=n.size&&n.size!=="md"?` navbar-brand-${n.size}`:"";if(n.logo||n.icon||n.tagline){let c="";n.logo?c+=`<img src="${n.logo}" class="navbar-brand-logo" alt="${n.text||""}">`:n.icon&&(c+=`<span data-icon="${n.icon}" style="width:1.1em;height:1.1em;margin-right:.35em;vertical-align:middle;"></span>`),n.text&&(c+=`<span class="navbar-brand-text${t}">${n.text}</span>`),n.tagline&&(c+=`<small class="navbar-brand-tagline">${n.tagline}</small>`),n.html=c}else t&&n.text&&(n.html=`<span class="navbar-brand-text${t}">${n.text}</span>`);const e=x.position==="floating",i=x.orientation==="vertical";Domma.elements.navbar("#site-navbar",{brand:n,items:s(x.items||[]),variant:x.variant||"dark",position:e?"static":x.position||"sticky",collapsible:!0,appearOnHover:!!x.appearOnHover}),Domma.icons.scan("#site-navbar");const p=_.get(0);p.classList.add(`dm-nav-align-${Q(x)||"centre"}`),i&&_.get(0).classList.add("dm-nav-vertical",x.side==="right"?"dm-nav-vertical-right":"dm-nav-vertical-left"),O(p,x),I(p,x.class),e&&(Y(_.get(0),x),Z(_.get(0).parentNode||document));const y=p.querySelector(".navbar-toggle"),u=p.querySelector(".navbar-collapse");if(y&&u){let c=null;const g=()=>{u.classList.contains("show")&&y.click()},r=()=>{c&&(c.remove(),c=null),document.body.classList.remove("dm-drawer-open")},a=()=>{c||(c=document.createElement("div"),c.className="dm-navbar-backdrop",c.addEventListener("click",g),document.body.appendChild(c),document.body.classList.add("dm-drawer-open"))};y.addEventListener("click",()=>{requestAnimationFrame(()=>{u.classList.contains("show")?a():r()})}),u.addEventListener("click",d=>{d.target.closest("a")&&requestAnimationFrame(r)}),document.addEventListener("keydown",d=>{d.key==="Escape"&&u.classList.contains("show")&&g()})}const m=p.querySelector(".navbar-nav");if(m){const c=Array.from(m.children);let g=0;for(const r of x.items||[])if(!(r&&r.type==="separator")){if(r&&r.type==="spacer"){const a=document.createElement("li"),d=["xs","sm","md","lg","xl","flex"].includes(r.size)?r.size:"md";a.className=`dm-menu-spacer dm-menu-spacer--${d}`,a.setAttribute("aria-hidden","true"),m.insertBefore(a,c[g]||null);continue}g++}}const b=o(x.items||[]);p.querySelectorAll(".navbar-link[data-index], .navbar-dropdown-item[data-index]").forEach(c=>{const g=b[parseInt(c.dataset.index,10)];if(!g)return;const r=c.dataset.subindex!=null,a=r&&Array.isArray(g.items)?g.items[parseInt(c.dataset.subindex,10)]:g;if(!a)return;a.colour&&j(c,a.colour),a.pill&&!r&&G(c,a.pill),U(c,a),I(c.closest("li"),a.class);const d=B(a.badge);d&&c.appendChild(d),R(a.url,location.pathname)&&(c.classList.add("active"),c.setAttribute("aria-current","page"))})}const C=$("#site-footer");if(C.length){let e=function(a){return String(a??"").replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""")},i=function(a){return e(a).replace(/'/g,"'")},p=function(a){if(a&&a.type==="separator")return'<li class="footer-sep"><hr></li>';if(a&&a.type==="spacer")return`<li class="dm-menu-spacer dm-menu-spacer--${["xs","sm","md","lg","xl","flex"].includes(a.size)?a.size:"md"}" aria-hidden="true"></li>`;const d=i(a.url||"#"),l=e(a.text||""),w=Array.isArray(a.items)&&a.items.length?`<ul>${a.items.map(p).join("")}</ul>`:"",v=K(a),D=[v.hover&&` data-menu-item-hover="${i(v.hover)}"`,v.active&&` data-menu-item-active="${i(v.active)}"`].filter(Boolean).join(""),M=v.accent?` style="--dm-menu-highlight:${i(v.accent)}"`:"",k=P(a.class).join(" ");return`<li${k?` class="${i(k)}"`:""}><a href="${d}"${D}${M}>${l}</a>${w}</li>`},y=function(a,d){return(a||[]).forEach(l=>{l&&(l.type==="separator"||l.type==="spacer")||(d.push(l),Array.isArray(l.items)&&l.items.length&&y(l.items,d))}),d};const n=f.social||{},t={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 s='<div class="footer-inner container">';const o=window.__CMS_FOOTER__||{primary:[],legal:[],copyright:""};if(o.copyright&&(s+=`<p>${e(o.copyright)}</p>`),o.primary.length&&(s+=`<nav class="footer-nav footer-nav--primary"><ul>${o.primary.map(p).join("")}</ul></nav>`),o.legal.length&&(s+=`<nav class="footer-nav footer-nav--legal"><ul>${o.legal.map(p).join("")}</ul></nav>`),f.footer){const a=Object.keys(t).filter(l=>n[l]),d=P(f.footer.socialClass).join(" ");a.length&&(s+='<div class="footer-social">',a.forEach(l=>{const{label:w,svg:v}=t[l];s+=`<a href="${n[l]}" target="_blank" rel="noopener noreferrer" aria-label="${w}" class="footer-social-link${d?` ${i(d)}`:""}">${v}</a>`}),s+="</div>")}s+="</div>",C.html(s),I(C.get(0),f.footer?.class);const u=[...y(o.primary,[]),...y(o.legal,[])];C.get(0).querySelectorAll(".footer-nav a").forEach((a,d)=>{const l=u[d];if(!l)return;l.colour&&j(a,l.colour);const w=B(l.badge);w&&a.appendChild(w),R(l.url,location.pathname)&&(a.classList.add("is-active"),a.setAttribute("aria-current","page"))}),O(C.get(0).querySelector(".footer-nav--primary"),{highlight:o.primaryHighlight}),O(C.get(0).querySelector(".footer-nav--legal"),{highlight:o.legalHighlight}),I(C.get(0).querySelector(".footer-nav--primary"),o.primaryClass),I(C.get(0).querySelector(".footer-nav--legal"),o.legalClass);const b=f.footer?.showMotionToggle!==!1,c=S.get("reduced_motion"),g=c!==null?!!c:!!(window.matchMedia&&window.matchMedia("(prefers-reduced-motion: reduce)").matches),r=C.get(0).querySelector(".footer-inner");if(b&&r){const a=document.createElement("input");a.type="checkbox",a.className="form-switch-input",a.id="dm-motion-switch",a.checked=g,a.addEventListener("change",function(){S.set("reduced_motion",this.checked),window.location.reload()});const d=document.createElement("span");d.className="form-switch-label",d.textContent="Reduce motion";const l=document.createElement("label");l.className="form-switch footer-motion-switch",l.title="Reduce motion",l.appendChild(a),l.appendChild(d),r.appendChild(l)}}$("#site-sidebar").length&&Domma.elements.sidebar("#site-sidebar",{autoGenerate:!0,selector:"h2, h3",collapsible:!1,push:!0,contentSelector:".site-content"}),Domma.icons.scan(),Domma.syntax?.scan&&Domma.syntax.scan();const h=$(".page-body");if(h.length){h.find(".accordion").each(function(){Domma.elements.accordion(this,{allowMultiple:this.dataset.multi==="true"})}),h.find(".tabs").each(function(){Domma.elements.tabs(this)}),h.find(".carousel").each(function(){const n={autoplay:this.dataset.autoplay==="true",interval:parseInt(this.dataset.interval,10)||5e3,loop:this.dataset.loop!=="false",animation:this.dataset.animation||"slide"};if(this.dataset.animationDuration){const t=parseInt(this.dataset.animationDuration,10);Number.isFinite(t)&&(n.animationDuration=t)}this.dataset.animationEasing&&(n.animationEasing=this.dataset.animationEasing),this.dataset.ctx&&(this._dmCtxSlides=Array.from(this.querySelectorAll(".carousel-slide")).map(t=>t.cloneNode(!0)),this._dmCarouselOpts=n),this._dmCarousel=Domma.elements.carousel(this,n)}),h.find(".dm-countdown").each(function(){const n={autoStart:!0},t=String(this.dataset.format||"").trim(),s=String(this.dataset.to||"").trim(),o=/^(\d{4})-(\d{2})-(\d{2})$/.exec(s),e=o?new Date(+o[1],+o[2]-1,+o[3]).getTime():s?new Date(s).getTime():NaN;Number.isFinite(e)?n.duration=Math.max(0,e-Date.now()):this.dataset.duration&&(n.duration=(parseInt(this.dataset.duration,10)||0)*1e3);const i=/^dd:/i.test(t);n.format=i?"hh:mm:ss":t||(n.duration>=36e5?"hh:mm:ss":"mm:ss");const p=this,y=m=>String(m).padStart(2,"0"),u=m=>{const b=Math.max(0,Math.floor(m/1e3)),c=p.querySelector(".dm-timer-time");c&&(c.textContent=`${y(Math.floor(b/86400))}:${y(Math.floor(b%86400/3600))}:${y(Math.floor(b%3600/60))}:${y(b%60)}`)};i&&(n.onTick=u),Domma.elements.timer(this,n),i&&u(n.duration||0)}),h.find("[data-tooltip]").each(function(){Domma.elements.tooltip(this,{content:$(this).data("tooltip"),position:$(this).data("tooltip-position")||"top"})}),h.find(".dm-progression").each(function(){const n=Array.from(this.querySelectorAll(".dm-progression-item")).map(o=>{const e={title:o.querySelector(".dm-progression-item-title")?.textContent?.trim()||"",description:o.querySelector(".dm-progression-item-body")?.innerHTML?.trim()||""};return o.dataset.date&&(e.date=o.dataset.date),o.dataset.status&&(e.status=o.dataset.status),o.dataset.entryId&&(e._entryId=o.dataset.entryId),e}),t=this.dataset.layout==="centred"?"centered":this.dataset.layout||"vertical",s=Domma.elements.progression(this,{layout:t,theme:this.dataset.theme||"minimal",mode:this.dataset.mode||"timeline",statusIcons:!0,allowHtml:!0,...n.length?{items:n}:{}});n.length&&(this._dmCtx={instance:s,items:n})});try{Domma.effects.reveal(".page-body .hero",{animation:"slide-up",duration:480,threshold:.06,stagger:60,once:!1})}catch{}document.querySelectorAll(".page-body .row[data-reveal]").forEach(n=>{const t=n.dataset.revealMode||"stagger",s=n.dataset.revealAnimation||"slide-up",o=parseInt(n.dataset.revealDuration,10)||400,e=parseInt(n.dataset.revealStagger,10)||60,i=parseInt(n.dataset.revealDelay,10)||0,p=n.dataset.revealDirection==="rtl",y=Array.from(n.children),u={"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)"};y.forEach((m,b)=>{m.style.opacity="0",m.style.transform=u[s]||"",m.style.transition=`opacity ${o}ms ease, transform ${o}ms ease`;const c=p?y.length-1-b:b;m.style.transitionDelay=t==="stagger"?`${i+c*e}ms`:`${i}ms`}),requestAnimationFrame(()=>requestAnimationFrame(()=>{const m=new IntersectionObserver(b=>{b.forEach(c=>{c.isIntersecting&&(c.target.offsetWidth,c.target.style.opacity="1",c.target.style.transform="none",m.unobserve(c.target))})},{threshold:.1});y.forEach(b=>m.observe(b))}))}),h.find(".card[data-collapsible]").each(function(){const n=this.querySelector(".card-header");n&&n.addEventListener("click",()=>this.classList.toggle("is-collapsed"))}),h.find(".dm-so-trigger").each(function(){this.addEventListener("click",()=>{const n=this.dataset.soTarget,t=document.getElementById(n);if(!t)return;const s=E.slideover({title:t.dataset.soTitle||"",size:t.dataset.soSize||"md",position:t.dataset.soPosition||"right"});t.style.display="",s.setContent(t),s.open()})})}if(typeof $.setup=="function"){const n=Object.assign({},window.__CMS_DCONFIG__||{});if(document.querySelectorAll(".dm-page-config[data-config]").forEach(t=>{try{const s=N(t.dataset.config);Object.assign(n,s)}catch{}}),Object.keys(n).length>0){const t={};for(const[s,o]of Object.entries(n)){const e=o?.events?.click,{confirm:i,toast:p,alert:y,prompt:u,...m}=e||{};i||p||y||u?($(s).on("click",async function(c){if(c.preventDefault(),i&&!await E.confirm(i))return;let g=null;if(!(u&&(g=await E.prompt(u,{inputPlaceholder:m.promptPlaceholder||"",inputValue:m.promptDefault||""}),g===null))){if(m.target){const r=$(m.target);m.toggleClass&&r.toggleClass(m.toggleClass),m.addClass&&r.addClass(m.addClass),m.removeClass&&r.removeClass(m.removeClass),g!==null&&(m.setText&&r.text(g),m.setVal&&r.val(g),m.setAttr&&r.attr(m.setAttr,g))}m.href&&(window.location.href=m.href),p&&E.toast(p,{type:m.toastType||"success"}),y&&E.alert(y)}}),Object.keys(m).length&&(t[s]={...o,events:{...o.events,click:m}})):t[s]=o}$.setup(t)}}h.length&&q(h.get(0))}),window.wireCTAButtons=q;function q(x){x.querySelectorAll(".dm-cta-trigger").forEach(f=>{f.__ctaWired||(f.__ctaWired=!0,f.addEventListener("click",async()=>{const _=f.dataset.action,C=f.dataset.entry,A=f.dataset.confirm;let h=S.get("auth_token");if(!h){E.toast("Please log in to perform this action.",{type:"warning"});return}if(A&&!await E.confirm(A))return;const n=Array.from(f.childNodes).map(s=>s.cloneNode(!0));f.disabled=!0,f.textContent="Running\u2026";const t=s=>fetch(`/api/actions/${_}/public`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${s}`},body:JSON.stringify(f.dataset.scope?{entryId:C,scope:f.dataset.scope}:{entryId:C})});try{let s=await t(h);if(s.status===401){const e=S.get("auth_refresh_token");if(e){const i=await fetch("/api/auth/refresh",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({refreshToken:e})});if(i.ok){const{token:p}=await i.json();S.set("auth_token",p),h=p,s=await t(h)}}}const o=await s.json().catch(()=>({}));if(!s.ok)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{f.disabled=!1,f.textContent="",n.forEach(s=>f.appendChild(s)),Domma.icons.scan(f)}}))})}(function(){const f=document.querySelectorAll('.dm-collection-hydrate[data-collection-scope="mine"]');if(!f.length)return;const _=S.get("auth_token");function C(h,n){const t=document.createElement("div");t.className="dm-collection-display dm-collection-empty";const s=document.createElement("p");if(n){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=h;return t.appendChild(s),t}function A(h,n){const t=document.createElement("template");t.innerHTML=n||"",h.replaceWith(t.content)}f.forEach(async h=>{const n=h.dataset.collectionAttrs;if(!n)return;if(!_){h.replaceChildren(C("",!0));return}const t=s=>fetch("/api/collections/render-scope",{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${s}`},body:JSON.stringify({attrs:n})});try{let s=_,o=await t(s);if(o.status===401){const i=S.get("auth_refresh_token");if(i){const p=await fetch("/api/auth/refresh",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({refreshToken:i})});if(p.ok){const{token:y}=await p.json();S.set("auth_token",y),s=y,o=await t(s)}}}if(!o.ok){const{error:i}=await o.json().catch(()=>({}));h.replaceChildren(C(i||"Unable to load entries.",!1));return}const{html:e}=await o.json();A(h,e),window.DommaCollectionBrowser?.hydrate&&window.DommaCollectionBrowser.hydrate(document)}catch{h.replaceChildren(C("Unable to load entries.",!1))}}),setTimeout(()=>{typeof Domma<"u"&&Domma.icons?.scan&&Domma.icons.scan(),typeof Domma<"u"&&Domma.syntax?.scan&&Domma.syntax.scan(),q(document.body)},100)})(),(function(){const f=document.querySelectorAll("[data-collection-table]");!f.length||typeof T>"u"||!T.create||f.forEach(_=>{let C;try{C=N(_.dataset.payload)}catch{return}const{columns:A,rows:h,search:n,sortable:t,exportable:s,pageSize:o,empty:e,ctaConfig:i,slug:p,exportCfg:y}=C;if(!A?.length)return;if(i){const b=A.findIndex(c=>c.key==="_cta");b!==-1&&(A[b]={key:"_cta",title:"",render:(c,g)=>{const r=document.createElement("button");if(r.className=`btn btn-${i.style||"primary"} dm-cta-trigger`,r.dataset.action=i.action||"",i.scope&&(r.dataset.scope=i.scope),r.dataset.entry=g._entryId||"",i.confirm&&(r.dataset.confirm=i.confirm),i.icon){const a=document.createElement("span");a.dataset.icon=i.icon,r.appendChild(a),r.appendChild(document.createTextNode(" "))}return r.appendChild(document.createTextNode(i.label||"Run")),r}})}const u="col-table-"+Math.random().toString(36).slice(2,7),m=document.createElement("div");m.id=u,_.replaceChildren(m),T.create("#"+u,{data:h,columns:A,search:n,sortable:t,exportable:s,pageSize:o,emptyMessage:e}),i&&q(m),_._dmTable={columns:A,rows:h,slug:p,exportCfg:y,render(b){T.create("#"+u,{data:b,columns:A,search:n,sortable:t,exportable:s,pageSize:o,emptyMessage:e}),i&&q(m)}}})})(),(function(){const f=document.querySelectorAll("[data-form-inline]");if(!f.length||typeof F>"u")return;function _(t){const s=t&&t.logic;return!!(s&&(s.visibility||s.requirement))}function C(t,s){const o={};return(t||[]).forEach(e=>{if(e.type==="page-break"||e.type==="spacer"||!e.name)return;const i=e.type==="chooser"||!!(e.variant&&["select","radio","checkbox-group"].includes(e.type)),p=e.type==="checkbox"?"boolean":e.type==="date"||e.type==="icon"||e.type==="image"?"string":i?"chooser":e.type,y={...e.formConfig||{}};y.span==="full"&&s&&(y.span=s);const u={type:p,required:_(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},...y}};e.minLength!==void 0&&(u.minLength=e.minLength),e.maxLength!==void 0&&(u.maxLength=e.maxLength),e.min!==void 0&&(u.min=e.min),e.max!==void 0&&(u.max=e.max),i&&(e.variant&&(u.variant=e.variant),(e.multiple||e.type==="checkbox-group")&&(u.multiple=!0),e.density&&(u.density=e.density),e.columns&&(u.columns=e.columns),e.accent&&(u.accent=e.accent),e.accentStyle&&(u.accentStyle=e.accentStyle),e.glow&&(u.glow=!0),e.glowColour&&(u.glowColour=e.glowColour),e.shadow&&(u.shadow=e.shadow),e.shadowColour&&(u.shadowColour=e.shadowColour)),o[e.name]=u}),o}function A(t){const s={};return(t||[]).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 h(t,s){(s||[]).forEach(o=>{if(o.type!=="date"||!o.name)return;const e=t.querySelector(`[name="${o.name}"]`);e&&e.type!=="date"&&(e.type="date")})}function n(t,s,o){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=o?"color:var(--dm-danger);margin-top:.75rem;":"color:var(--dm-success);margin-top:.75rem;"}f.forEach(t=>{let s;try{s=N(t.dataset.formInline)}catch{return}const o=s.fields||[],e=s.settings||{},i=Number(e.columns)===3?3:2,p=e.layout||"grid",y=o.some(r=>r.type==="page-break"),u=async r=>{if(t.dataset.triggerBlocked==="true"){const a=t._formLogicRuntime,d=a&&a.submitBlock?a.submitBlock().reasons:[];n(t,d.join(" ")||"This form cannot be submitted yet.",!0);const l=new Error("Blocked by a form trigger");throw l.formSubmitFailed=!0,l}try{const a=t.querySelector(".fb-form-honeypot"),d=a?.querySelector('input[type="text"]')||null,l=a?.querySelector('[name="_t"]')||null,w=Object.assign({},r);delete w._t,a?.dataset.hpName&&delete w[a.dataset.hpName],d!==null&&(w._hp=d.value),l!==null&&(w._t=l.value);const v=await H.post(`/api/forms/submit/${s.slug}`,w);if(v&&v.error){n(t,v.error,!0);const D=new Error(v.error);throw D.formSubmitFailed=!0,D}if(v?.redirect){window.location.href=v.redirect;return}for(;t.firstChild;)t.removeChild(t.firstChild);n(t,v?.message||e.successMessage||"Thank you for your submission.",!1)}catch(a){throw a.formSubmitFailed||n(t,a.message||"Submission failed. Please try again.",!0),a}},m=Date.now();function b(r){const a=r.querySelector("form");if(!a||a.querySelector(".fb-form-honeypot"))return;const d=document.createElement("div");d.className="fb-form-honeypot",d.setAttribute("aria-hidden","true");const l=document.createElement("input");l.name=a.querySelector('[name="website"]')?"_website":"website",d.dataset.hpName=l.name,l.type="text",l.tabIndex=-1,l.autocomplete="url",l.placeholder="https://",d.appendChild(l);const w=document.createElement("input");w.name="_t",w.type="hidden",w.value=m,d.appendChild(w),a.appendChild(d)}function c(r){if(!window.FormLogicEngine||!o.some(d=>d.logic||Array.isArray(d.triggers)&&d.triggers.length))return null;const a=new window.FormLogicEngine.FormLogicRuntime(s,t,r?{baseValues:r}:void 0);if(a.init(),t._formLogicRuntime=a,t._formLogicObserved)return a;if(t._formLogicObserved=!0,t.parentNode&&typeof MutationObserver<"u"){const d=new MutationObserver(function(l){for(const w of l)for(const v of w.removedNodes)if(v===t||v.nodeType===1&&v.contains&&v.contains(t)){t._formLogicRuntime&&t._formLogicRuntime.destroy(),d.disconnect();return}});d.observe(t.parentNode,{childList:!0,subtree:!1}),t._formLogicObserver=d}return a}let g=!1;if(t.addEventListener("dm-form-end",r=>{const a=r.detail?.validation;if(a&&!a.valid){W(t,a.missing),J(a);return}g||(g=!0,u(r.detail?.values||{}).catch(()=>{}).finally(()=>{g=!1}))}),y&&F.wizard){const r=ee(o,s.title),a=r.map(k=>({title:k.title,description:k.description,fields:C(k.fields,i)})),d=r.map(k=>k.fields);let l=0,w=null,v=null;const D=k=>{const L=window.FormLogicEngine;if(!L||!L.validateFields)return;const z=L.validateFields(t,d[l]||[]);z.valid||(k.preventDefault(),k.stopImmediatePropagation(),W(t,z.missing),J(z))},M=()=>{const k=d[l]||o;ae(t,e),X(t,e,k),h(t,k),V(t,k),e.honeypot!==!1&&b(t),w&&w.destroy(),w=c(v?.getStepData?v.getStepData():void 0),t.querySelectorAll(".wizard-next, .wizard-submit").forEach(L=>L.addEventListener("click",D,!0))};v=F.wizard(t,{schema:{steps:a},layout:p,columns:i,finishText:e.submitText||"Finish",onSubmit:u,onStepChange:k=>{l=k,setTimeout(M,0)}}),t.addEventListener("dm-form-trigger",k=>{if(k.detail?.type!=="goto-step"||!v?.goToStep)return;const L=Number(k.detail.action.step);!Number.isFinite(L)||L<0||L>=a.length||L!==l&&v.goToStep(L)}),Promise.resolve(v).then(M)}else if(F.render){const r=F.render(t,C(o,i),A(o),{submitText:e.submitText||"Submit",layout:p,columns:i,onSubmit:u});Promise.resolve(r).then(function(){te(t,e,p),X(t,e,o),h(t,o),V(t,o),e.honeypot!==!1&&b(t),c()})}}),$(document).on("click",".dm-banner__dismiss",function(){$(this).closest(".dm-banner").remove()})})();
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export function decodeBase64Json(e){const t=Uint8Array.from(atob(e),o=>o.charCodeAt(0));return JSON.parse(new TextDecoder("utf-8",{fatal:!0}).decode(t))}
|
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
* in step with its schema. Core has no need: seedAll() rewrites every preset's
|
|
30
30
|
* schema.json on each boot, which is the same job done earlier.
|
|
31
31
|
*/
|
|
32
|
+
import {registerToolTrash, trashToolEntry} from '../../services/tool-trash.js';
|
|
32
33
|
import {authenticate as defaultAuthenticate, requirePermission as defaultRequirePermission} from '../../middleware/auth.js';
|
|
33
34
|
import {createEntry, deleteEntry, getEntry, listEntries, updateEntry} from '../../services/collections.js';
|
|
34
35
|
import {hooks} from '../../services/hooks.js';
|
|
@@ -248,6 +249,8 @@ export async function contactsRoutes(fastify, opts = {}) {
|
|
|
248
249
|
const requirePermission = opts.requirePermission || defaultRequirePermission;
|
|
249
250
|
const holdsManageGroups = opts.holdsManageGroups || defaultHoldsManageGroups;
|
|
250
251
|
|
|
252
|
+
registerToolTrash(fastify, {slug: CONTACTS_SLUG, resource: 'contacts', itemPath: '/contacts', authenticate, requirePermission, project: toContact, onPurge: entry => hooks.emit('contact:deleted', {id: entry.id, userId: entry.data.userId})});
|
|
253
|
+
|
|
251
254
|
function userId(request) {
|
|
252
255
|
return request.user?.id ?? request.user?.sub ?? null;
|
|
253
256
|
}
|
|
@@ -384,18 +387,14 @@ export async function contactsRoutes(fastify, opts = {}) {
|
|
|
384
387
|
return reply.code(400).send({ error: 'ids array is required' });
|
|
385
388
|
}
|
|
386
389
|
|
|
387
|
-
|
|
388
|
-
for (const id of ids) {
|
|
389
|
-
const entry = await getEntry(CONTACTS_SLUG, id)
|
|
390
|
-
if (!entry) continue;
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
});
|
|
394
|
-
hooks.emit('contact:deleted', {id, userId: entry.data.userId ?? null});
|
|
395
|
-
deleted++;
|
|
390
|
+
const deletedIds = [], failedIds = [];
|
|
391
|
+
for (const id of [...new Set(ids)]) {
|
|
392
|
+
const entry = await getEntry(CONTACTS_SLUG, id);
|
|
393
|
+
if (!entry || !uid || entry.data.userId !== uid) { failedIds.push(id); continue; }
|
|
394
|
+
try { await trashToolEntry(CONTACTS_SLUG, entry); deletedIds.push(id); }
|
|
395
|
+
catch { failedIds.push(id); }
|
|
396
396
|
}
|
|
397
|
-
|
|
398
|
-
return reply.send({ deleted });
|
|
397
|
+
return reply.send({deleted: deletedIds.length, deletedIds, failedIds});
|
|
399
398
|
});
|
|
400
399
|
|
|
401
400
|
/** DELETE /api/contacts/contacts/:id */
|
|
@@ -407,9 +406,8 @@ export async function contactsRoutes(fastify, opts = {}) {
|
|
|
407
406
|
if (!entry) return reply.code(404).send({error: 'Contact not found'});
|
|
408
407
|
if (uid && entry.data.userId !== uid) return reply.code(404).send({error: 'Contact not found'});
|
|
409
408
|
|
|
410
|
-
await
|
|
411
|
-
//
|
|
412
|
-
hooks.emit('contact:deleted', {id, userId: entry.data.userId ?? null});
|
|
409
|
+
await trashToolEntry(CONTACTS_SLUG, entry);
|
|
410
|
+
// Keep related links and Pro fields for restore.
|
|
413
411
|
return reply.send({ ok: true });
|
|
414
412
|
});
|
|
415
413
|
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Notes API - /api/notes
|
|
3
3
|
*
|
|
4
|
-
* Was the `notes` plugin until 0.67.0.
|
|
5
|
-
* where it lives and how it is gated.
|
|
4
|
+
* Was the `notes` plugin until 0.67.0. Personal entries now use recoverable Trash; normal reads omit trashed rows.
|
|
6
5
|
*
|
|
7
6
|
* **Per-user scope.** Entries carry a `userId` and every read is filtered to the
|
|
8
7
|
* caller's own. The plugin made this configurable (`scope: 'user' | 'site'`) and
|
|
@@ -15,9 +14,10 @@
|
|
|
15
14
|
* `setupPlugin()` read `schema.slug`, so the live collection has always been
|
|
16
15
|
* called this, and existing entries are found only if it stays exactly so.
|
|
17
16
|
*/
|
|
17
|
+
import {registerToolTrash, trashToolEntry} from '../../services/tool-trash.js';
|
|
18
18
|
import {ago, clip, plural} from '../../services/sidebarBadges.js';
|
|
19
19
|
import {authenticate as defaultAuthenticate, requirePermission as defaultRequirePermission} from '../../middleware/auth.js';
|
|
20
|
-
import {createEntry,
|
|
20
|
+
import {createEntry, getEntry, listEntries, updateEntry} from '../../services/collections.js';
|
|
21
21
|
import {hooks} from '../../services/hooks.js';
|
|
22
22
|
|
|
23
23
|
const SLUG = 'notes-notes';
|
|
@@ -45,6 +45,8 @@ export async function notesRoutes(fastify, opts = {}) {
|
|
|
45
45
|
const requirePermission = opts.requirePermission || defaultRequirePermission;
|
|
46
46
|
|
|
47
47
|
|
|
48
|
+
registerToolTrash(fastify, {slug: SLUG, resource: 'notes', itemPath: '/items', authenticate, requirePermission, project: toNote, onPurge: entry => hooks.emit('note:deleted', {id: entry.id, userId: entry.data.userId})});
|
|
49
|
+
|
|
48
50
|
function userId(request) {
|
|
49
51
|
return request.user?.id ?? request.user?.sub ?? null;
|
|
50
52
|
}
|
|
@@ -149,9 +151,8 @@ export async function notesRoutes(fastify, opts = {}) {
|
|
|
149
151
|
if (!entry) return reply.code(404).send({error: 'Note not found'});
|
|
150
152
|
if (uid && entry.data.userId !== uid) return reply.code(404).send({error: 'Note not found'});
|
|
151
153
|
|
|
152
|
-
await
|
|
153
|
-
//
|
|
154
|
-
hooks.emit('note:deleted', {id, userId: entry.data.userId ?? null});
|
|
154
|
+
await trashToolEntry(SLUG, entry);
|
|
155
|
+
// Related links remain available if this note is restored.
|
|
155
156
|
return reply.send({ success: true });
|
|
156
157
|
});
|
|
157
158
|
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Todo API - /api/todo
|
|
3
3
|
*
|
|
4
|
-
* Was the `todo` plugin until 0.67.0.
|
|
5
|
-
* where it lives and how it is gated.
|
|
4
|
+
* Was the `todo` plugin until 0.67.0. Personal entries now use recoverable Trash; normal reads omit trashed rows.
|
|
6
5
|
*
|
|
7
6
|
* **Per-user scope.** Entries carry a `userId` and every read is filtered to the
|
|
8
7
|
* caller's own. The plugin made this configurable (`scope: 'user' | 'site'`) and
|
|
@@ -15,9 +14,10 @@
|
|
|
15
14
|
* `setupPlugin()` read `schema.slug`, so the live collection has always been
|
|
16
15
|
* called this, and existing entries are found only if it stays exactly so.
|
|
17
16
|
*/
|
|
17
|
+
import {registerToolTrash, trashToolEntry} from '../../services/tool-trash.js';
|
|
18
18
|
import {clip, plural} from '../../services/sidebarBadges.js';
|
|
19
19
|
import {authenticate as defaultAuthenticate, requirePermission as defaultRequirePermission} from '../../middleware/auth.js';
|
|
20
|
-
import {createEntry,
|
|
20
|
+
import {createEntry, getEntry, listEntries, updateEntry} from '../../services/collections.js';
|
|
21
21
|
import {registerCalendarSource} from '../../services/hooks.js';
|
|
22
22
|
import {getPermissionsFor} from '../../services/roles.js';
|
|
23
23
|
|
|
@@ -83,6 +83,8 @@ export async function todoRoutes(fastify, opts = {}) {
|
|
|
83
83
|
const requirePermission = opts.requirePermission || defaultRequirePermission;
|
|
84
84
|
|
|
85
85
|
|
|
86
|
+
registerToolTrash(fastify, {slug: SLUG, resource: 'todo', itemPath: '/items', authenticate, requirePermission, project: toTodo, onChange: tell});
|
|
87
|
+
|
|
86
88
|
function userId(request) {
|
|
87
89
|
return request.user?.id ?? request.user?.sub ?? null;
|
|
88
90
|
}
|
|
@@ -213,7 +215,7 @@ export async function todoRoutes(fastify, opts = {}) {
|
|
|
213
215
|
const uid = userId(request);
|
|
214
216
|
const all = await loadAll(uid);
|
|
215
217
|
const completed = all.filter(t => t.status === 'completed');
|
|
216
|
-
await Promise.all(completed.map(t =>
|
|
218
|
+
await Promise.all(completed.map(async t => trashToolEntry(SLUG, await getEntry(SLUG, t.id))));
|
|
217
219
|
if (completed.length) tell(uid);
|
|
218
220
|
return reply.send({success: true, removed: completed.length});
|
|
219
221
|
});
|
|
@@ -227,7 +229,7 @@ export async function todoRoutes(fastify, opts = {}) {
|
|
|
227
229
|
if (!entry) return reply.code(404).send({error: 'Todo not found'});
|
|
228
230
|
if (uid && entry.data.userId !== uid) return reply.code(404).send({error: 'Todo not found'});
|
|
229
231
|
|
|
230
|
-
await
|
|
232
|
+
await trashToolEntry(SLUG, entry);
|
|
231
233
|
tell(entry.data.userId);
|
|
232
234
|
return reply.send({ success: true });
|
|
233
235
|
});
|
|
@@ -16,6 +16,7 @@ import {typedEntryData} from './entryValues.js';
|
|
|
16
16
|
import {toCsv} from '../../public/js/collection-export.mjs';
|
|
17
17
|
|
|
18
18
|
const COLLECTIONS_DIR = path.resolve(config.content.collectionsDir);
|
|
19
|
+
const TOOL_TRASH_SLUGS = new Set(['notes-notes', 'todo-items', 'contacts-contacts']);
|
|
19
20
|
|
|
20
21
|
// ---------------------------------------------------------------------------
|
|
21
22
|
// Internal helpers (schema only)
|
|
@@ -352,6 +353,7 @@ export async function validateReferences(schema, data) {
|
|
|
352
353
|
* @param {string} [opts.search] - Substring match across all fields
|
|
353
354
|
* @param {Record<string, unknown>} [opts.filter] - Structured filter; see filterEngine.js
|
|
354
355
|
* @param {boolean} [opts.resolveRefs=false] - Batch-resolve reference fields to display labels
|
|
356
|
+
* @param {boolean} [opts.includeToolTrash=false] - Internal recovery/maintenance reads only
|
|
355
357
|
* @returns {Promise<{ entries: object[], total: number, page: number, limit: number }>}
|
|
356
358
|
* @throws {Error} If collection slug does not exist
|
|
357
359
|
*/
|
|
@@ -359,6 +361,11 @@ export async function listEntries(slug, opts = {}) {
|
|
|
359
361
|
const schema = await getCollection(slug);
|
|
360
362
|
if (!schema) throw new Error(`Collection "${slug}" not found`);
|
|
361
363
|
const adapter = await getAdapter(slug);
|
|
364
|
+
// Personal Tool trash is retained for recovery, but must stay out of
|
|
365
|
+
// normal lists, badges, notifications and contact-sharing integrations.
|
|
366
|
+
if (TOOL_TRASH_SLUGS.has(slug) && !opts.includeToolTrash) {
|
|
367
|
+
opts = {...opts, filter: {...opts.filter, _toolTrashedAt_exists: false}};
|
|
368
|
+
}
|
|
362
369
|
const result = await adapter.list(slug, opts);
|
|
363
370
|
if (opts.resolveRefs) {
|
|
364
371
|
const {resolveReferences} = await import('./references.js');
|
|
@@ -372,11 +379,13 @@ export async function listEntries(slug, opts = {}) {
|
|
|
372
379
|
*
|
|
373
380
|
* @param {string} slug
|
|
374
381
|
* @param {string} entryId
|
|
382
|
+
* @param {{includeToolTrash?: boolean}} [opts] - Internal recovery/maintenance reads only
|
|
375
383
|
* @returns {Promise<object|null>}
|
|
376
384
|
*/
|
|
377
|
-
export async function getEntry(slug, entryId) {
|
|
385
|
+
export async function getEntry(slug, entryId, opts = {}) {
|
|
378
386
|
const adapter = await getAdapter(slug);
|
|
379
|
-
|
|
387
|
+
const entry = await adapter.get(slug, entryId);
|
|
388
|
+
return TOOL_TRASH_SLUGS.has(slug) && entry?.data?._toolTrashedAt && !opts.includeToolTrash ? null : entry;
|
|
380
389
|
}
|
|
381
390
|
|
|
382
391
|
/**
|
|
@@ -1819,17 +1819,17 @@ function processEffectsBlocks(markdown) {
|
|
|
1819
1819
|
|
|
1820
1820
|
apply('firework', (attrStr, body) => {
|
|
1821
1821
|
const attrs = parseShortcodeAttrs(attrStr);
|
|
1822
|
+
const flag = name => attrs[name] === true || attrs[name] === 'true';
|
|
1822
1823
|
const classes = ['firework'];
|
|
1823
|
-
if (attrs
|
|
1824
|
-
if (
|
|
1825
|
-
|
|
1826
|
-
if (
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
return `<div class="${classes.join(' ')}">${innerHtml}</div>\n`;
|
|
1824
|
+
for (const name of ['type', 'colour', 'size']) if (attrs[name]) classes.push(`firework-${escapeAttr(attrs[name])}`);
|
|
1825
|
+
if (flag('continuous')) classes.push('firework-continuous');
|
|
1826
|
+
const hover = flag('hover');
|
|
1827
|
+
if (hover) classes.push('firework-on-hover');
|
|
1828
|
+
// Native hover fireworks need a hit target; the base decoration ignores pointer events.
|
|
1829
|
+
const style = hover ? ' style="pointer-events:auto;--dm-fw-size:var(--dm-fw-particle-size)"' : '';
|
|
1830
|
+
const particles = hover ? '' : '<div class="firework-launch"></div><div class="firework-particles"></div>';
|
|
1831
|
+
const innerHtml = body === null ? '' : marked.parse(processCardBlocks(processGridBlocks(processInlineShortcodes(restore(body.trim())))));
|
|
1832
|
+
return `<div class="${classes.join(' ')}"${style}>${particles}${innerHtml}</div>\n`;
|
|
1833
1833
|
});
|
|
1834
1834
|
|
|
1835
1835
|
apply('fireworks', (_attrStr, body) => {
|
|
@@ -367,7 +367,7 @@ export async function renderPage(page, opts = {}) {
|
|
|
367
367
|
? `window.__CMS_CTXMENUS__ = ${JSON.stringify(ctxMenus).replace(/<\/script>/gi, '<\\/script>')};`
|
|
368
368
|
: '';
|
|
369
369
|
const ctxMenusModule = ctxMenus.length
|
|
370
|
-
? '<script src="/public/js/context-menus.js?v=
|
|
370
|
+
? '<script src="/public/js/context-menus.js?v=20261010-utf8-payloads" type="module"></script>'
|
|
371
371
|
: '';
|
|
372
372
|
|
|
373
373
|
const vars = {
|
|
@@ -1081,7 +1081,7 @@ export async function renderBlogPage(templatePath, data = {}, seoMeta = {}) {
|
|
|
1081
1081
|
? `window.__CMS_CTXMENUS__ = ${JSON.stringify(ctxMenus).replace(/<\/script>/gi, '<\\/script>')};`
|
|
1082
1082
|
: '';
|
|
1083
1083
|
const ctxMenusModule = ctxMenus.length
|
|
1084
|
-
? '<script src="/public/js/context-menus.js?v=
|
|
1084
|
+
? '<script src="/public/js/context-menus.js?v=20261010-utf8-payloads" type="module"></script>'
|
|
1085
1085
|
: '';
|
|
1086
1086
|
|
|
1087
1087
|
const vars = {
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Runs once on server boot. Idempotent - guarded by the existence of
|
|
5
5
|
* config/menus/admin-sidebar.json. Seeds the admin sidebar menu with the
|
|
6
|
-
* standard tree (
|
|
6
|
+
* standard tree (Dashboards, Projects, Content, Data, Tools, System,
|
|
7
7
|
* Documentation) and patches config/menu-locations.json to map the
|
|
8
8
|
* `admin-sidebar` slot.
|
|
9
9
|
*
|
|
@@ -21,7 +21,7 @@ const DEFAULT_CONFIG_DIR = path.resolve(path.dirname(__filename), '..', '..', 'c
|
|
|
21
21
|
|
|
22
22
|
const SEED_ITEMS = [
|
|
23
23
|
{
|
|
24
|
-
text: '
|
|
24
|
+
text: 'Dashboards', key: 'overview', icon: 'home',
|
|
25
25
|
items: [
|
|
26
26
|
{text: 'Dashboard', url: '#/', icon: 'home'}
|
|
27
27
|
]
|
|
@@ -319,13 +319,14 @@ export async function ensureDocumentationSubmenus(opts = {}) {
|
|
|
319
319
|
|
|
320
320
|
/** Keys for the folders the seed has always created, matched by their original name. */
|
|
321
321
|
const SEED_FOLDER_KEYS = {
|
|
322
|
-
overview: 'overview', projects: 'projects', content: 'content', data: 'data',
|
|
322
|
+
overview: 'overview', dashboards: 'overview', projects: 'projects', content: 'content', data: 'data',
|
|
323
323
|
tools: 'tools', system: 'system', documentation: 'documentation'
|
|
324
324
|
};
|
|
325
325
|
|
|
326
326
|
/**
|
|
327
327
|
* Give an existing install's top-level folders their stable `key`, and - once -
|
|
328
|
-
* a persisted Tools folder for enabled Tools to be placed into
|
|
328
|
+
* a persisted Tools folder for enabled Tools to be placed into, and - once -
|
|
329
|
+
* the name Dashboards for the folder that was Overview.
|
|
329
330
|
*
|
|
330
331
|
* - A folder is keyed only if it has no key yet and still carries one of the
|
|
331
332
|
* seed's names; a folder the admin made, or renamed before keys existed, is
|
|
@@ -361,6 +362,16 @@ export async function ensureFolderKeys(opts = {}) {
|
|
|
361
362
|
changed = true;
|
|
362
363
|
}
|
|
363
364
|
|
|
365
|
+
// The first folder was "Overview" until 1.6.6. Renamed once (`meta.dashboardsFolder`)
|
|
366
|
+
// and only while it still says Overview, so a name an admin gave it - even
|
|
367
|
+
// Overview again, later - stays. Its key is still `overview`.
|
|
368
|
+
if (!menu.meta?.dashboardsFolder && items.length) {
|
|
369
|
+
const first = items.find(n => isFolder(n) && n.key === 'overview');
|
|
370
|
+
if (first && String(first.text || '').trim() === 'Overview') first.text = 'Dashboards';
|
|
371
|
+
menu.meta = {...(menu.meta || {}), dashboardsFolder: true};
|
|
372
|
+
changed = true;
|
|
373
|
+
}
|
|
374
|
+
|
|
364
375
|
// An empty menu is left empty: the renderer's minimal fallback (Dashboard,
|
|
365
376
|
// Menus) is what an admin needs to repair it, and a lone Tools folder would
|
|
366
377
|
// stop that fallback from applying.
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import {deleteEntry, getEntry, listEntries, updateEntry} from './collections.js';
|
|
2
|
+
|
|
3
|
+
/** Keep the original row (including Pro fields and related IDs) until purged. */
|
|
4
|
+
export async function trashToolEntry(slug, entry) {
|
|
5
|
+
return updateEntry(slug, entry.id, {...entry.data, _toolTrashedAt: new Date().toISOString()});
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
/** Routes remain owner-scoped, even for administrators. Restore needs update. */
|
|
9
|
+
export function registerToolTrash(fastify, {slug, resource, itemPath, authenticate, requirePermission, project, onChange, onPurge}) {
|
|
10
|
+
const uid = request => request.user?.id ?? request.user?.sub ?? null;
|
|
11
|
+
const may = action => ({preHandler: [authenticate, requirePermission(resource, action)]});
|
|
12
|
+
const own = async request => {
|
|
13
|
+
const entry = await getEntry(slug, request.params.id, {includeToolTrash: true});
|
|
14
|
+
return uid(request) && entry?.data.userId === uid(request) && entry.data._toolTrashedAt ? entry : null;
|
|
15
|
+
};
|
|
16
|
+
fastify.get('/trash', may('read'), async request => {
|
|
17
|
+
if (!uid(request)) return [];
|
|
18
|
+
const {entries} = await listEntries(slug, {limit: 100000, includeToolTrash: true});
|
|
19
|
+
return entries.filter(e => e.data.userId === uid(request) && e.data._toolTrashedAt)
|
|
20
|
+
.sort((a, b) => String(b.data._toolTrashedAt).localeCompare(String(a.data._toolTrashedAt)))
|
|
21
|
+
.map(e => ({...project(e), trashedAt: e.data._toolTrashedAt}));
|
|
22
|
+
});
|
|
23
|
+
fastify.post(`${itemPath}/:id/restore`, may('update'), async (request, reply) => {
|
|
24
|
+
const entry = await own(request);
|
|
25
|
+
if (!entry) return reply.code(404).send({error: 'Item not found in Trash'});
|
|
26
|
+
const data = {...entry.data};
|
|
27
|
+
delete data._toolTrashedAt;
|
|
28
|
+
const restored = await updateEntry(slug, entry.id, data);
|
|
29
|
+
await onChange?.(uid(request));
|
|
30
|
+
return project(restored);
|
|
31
|
+
});
|
|
32
|
+
fastify.delete('/trash/:id', may('delete'), async (request, reply) => {
|
|
33
|
+
const entry = await own(request);
|
|
34
|
+
if (!entry) return reply.code(404).send({error: 'Item not found in Trash'});
|
|
35
|
+
await deleteEntry(slug, entry.id);
|
|
36
|
+
await onPurge?.(entry);
|
|
37
|
+
return {success: true};
|
|
38
|
+
});
|
|
39
|
+
}
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
{{#if fontLink}}{{fontLink}}{{/if}}
|
|
20
20
|
|
|
21
21
|
<!-- DommaJS CSS -->
|
|
22
|
-
<link rel="stylesheet" href="/dist/domma/domma.css">
|
|
22
|
+
<link rel="stylesheet" href="/dist/domma/domma.css?v=20261010-fireworks">
|
|
23
23
|
<link rel="stylesheet" href="/dist/domma/grid.css">
|
|
24
24
|
<link rel="stylesheet" href="/dist/domma/elements.css">
|
|
25
25
|
<link rel="stylesheet" href="/dist/domma/themes/domma-themes.css">
|
|
@@ -137,8 +137,8 @@
|
|
|
137
137
|
</script>
|
|
138
138
|
|
|
139
139
|
<!-- Site initialisation -->
|
|
140
|
-
<script src="/public/js/site.js?v=
|
|
141
|
-
<script src="/public/js/collection-context.js?v=
|
|
140
|
+
<script src="/public/js/site.js?v=20261010-utf8-payloads" type="module"></script>
|
|
141
|
+
<script src="/public/js/collection-context.js?v=20261010-utf8-payloads" type="module"></script>
|
|
142
142
|
{{ctxMenusModule}}
|
|
143
143
|
|
|
144
144
|
<!-- Floating menu panels: collapse to a hamburger, drag to move, resize.
|
|
@@ -155,7 +155,7 @@
|
|
|
155
155
|
<script src="/public/js/forms.js?v=20261009-form-classes" type="module"></script>
|
|
156
156
|
|
|
157
157
|
<!-- Core effects runtime -->
|
|
158
|
-
<script src="/public/js/effects.js?v=
|
|
158
|
+
<script src="/public/js/effects.js?v=20261010-motion-demos"></script>
|
|
159
159
|
|
|
160
160
|
<!-- Plugin body-end injection -->
|
|
161
161
|
{{bodyEndInject}}
|