domma-cms 0.43.1 → 0.45.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +53 -53
- package/README.md +27 -27
- package/admin/css/admin.css +1 -1
- package/admin/dist/domma/domma-tools.css +3 -3
- package/admin/dist/domma/domma-tools.min.js +3 -3
- package/admin/index.html +2 -2
- package/admin/js/app.js +1 -1
- package/admin/js/lib/card-builder.js +2 -2
- package/admin/js/lib/crud-tutorial.js +1 -1
- package/admin/js/lib/effect-defs.js +1 -1
- package/admin/js/lib/effects-builder.js +1 -1
- package/admin/js/lib/image-editor.js +1 -1
- package/admin/js/lib/markdown-toolbar.js +2 -2
- package/admin/js/lib/project-context.js +1 -1
- package/admin/js/lib/project-quick-create.js +1 -1
- package/admin/js/lib/scribe-composer.js +1 -1
- package/admin/js/lib/server-restart.js +1 -1
- package/admin/js/lib/shortcode-context-menu.js +2 -2
- package/admin/js/lib/simple-editor.js +1 -1
- package/admin/js/lib/themes.js +1 -1
- package/admin/js/lib/timeline-builder.js +1 -1
- package/admin/js/templates/action-editor.html +8 -8
- package/admin/js/templates/api-endpoint-editor.html +7 -7
- package/admin/js/templates/api-endpoints.html +1 -1
- package/admin/js/templates/api-reference.html +1 -1
- package/admin/js/templates/block-editor.html +6 -6
- package/admin/js/templates/collection-editor.html +1 -1
- package/admin/js/templates/component-editor.html +2 -2
- package/admin/js/templates/dashboard/cache.html +3 -3
- package/admin/js/templates/dashboard/journeys.html +3 -3
- package/admin/js/templates/dashboard/kpi-strip.html +3 -3
- package/admin/js/templates/dashboard/top-pages.html +1 -1
- package/admin/js/templates/dashboard/traffic-chart.html +1 -1
- package/admin/js/templates/docs/api-actions.html +1 -1
- package/admin/js/templates/docs/api-collections.html +4 -4
- package/admin/js/templates/docs/api-pages.html +2 -2
- package/admin/js/templates/docs/api-plugins.html +1 -1
- package/admin/js/templates/docs/api-settings.html +2 -2
- package/admin/js/templates/docs/api-users.html +1 -1
- package/admin/js/templates/docs/components-howto.html +4 -4
- package/admin/js/templates/docs/components-reference.html +21 -21
- package/admin/js/templates/docs/components-rules.html +14 -14
- package/admin/js/templates/docs/components-walkthrough.html +13 -13
- package/admin/js/templates/docs/tutorial-crud.html +49 -49
- package/admin/js/templates/docs/tutorial-forms.html +4 -4
- package/admin/js/templates/docs/tutorial-plugin.html +10 -10
- package/admin/js/templates/docs/usage-actions.html +6 -6
- package/admin/js/templates/docs/usage-cta-shortcode.html +9 -9
- package/admin/js/templates/docs/usage-dconfig.html +4 -4
- package/admin/js/templates/docs/usage-navigation.html +1 -1
- package/admin/js/templates/docs/usage-pages.html +1 -1
- package/admin/js/templates/docs/usage-shortcodes.html +24 -24
- package/admin/js/templates/docs/usage-users-roles.html +3 -3
- package/admin/js/templates/docs/usage-views.html +4 -4
- package/admin/js/templates/documentation.html +1 -1
- package/admin/js/templates/effects.html +25 -25
- package/admin/js/templates/form-editor.html +5 -5
- package/admin/js/templates/login.html +5 -5
- package/admin/js/templates/menu-editor.html +10 -10
- package/admin/js/templates/navigation.html +11 -11
- package/admin/js/templates/notifications.html +1 -1
- package/admin/js/templates/page-editor.html +4 -4
- package/admin/js/templates/pro-docs.html +14 -14
- package/admin/js/templates/role-editor.html +1 -1
- package/admin/js/templates/settings.html +10 -10
- package/admin/js/templates/tutorials.html +3 -3
- package/admin/js/templates/view-editor.html +15 -15
- package/admin/js/views/action-editor.js +1 -1
- package/admin/js/views/actions-list.js +1 -1
- package/admin/js/views/api-endpoint-editor.js +2 -2
- package/admin/js/views/api-endpoints.js +2 -2
- package/admin/js/views/api-tokens.js +3 -3
- package/admin/js/views/blocks.js +2 -2
- package/admin/js/views/collection-editor.js +1 -1
- package/admin/js/views/collection-entries.js +1 -1
- package/admin/js/views/collections.js +1 -1
- package/admin/js/views/component-editor.js +1 -1
- package/admin/js/views/components.js +4 -4
- package/admin/js/views/dashboard/widgets/cache.js +1 -1
- package/admin/js/views/dashboard/widgets/journeys.js +1 -1
- package/admin/js/views/dashboard/widgets/kpi-strip.js +1 -1
- package/admin/js/views/form-editor.js +7 -7
- package/admin/js/views/form-submissions.js +1 -1
- package/admin/js/views/forms.js +1 -1
- package/admin/js/views/layouts.js +1 -1
- package/admin/js/views/login.js +2 -2
- package/admin/js/views/menu-editor.js +3 -3
- package/admin/js/views/menu-locations.js +1 -1
- package/admin/js/views/menus.js +3 -3
- package/admin/js/views/navigation.js +8 -8
- package/admin/js/views/page-editor.js +40 -40
- package/admin/js/views/pages.js +3 -3
- package/admin/js/views/plugin-code.js +2 -2
- package/admin/js/views/plugins.js +1 -1
- package/admin/js/views/project-settings.js +1 -1
- package/admin/js/views/projects.js +1 -1
- package/admin/js/views/roles.js +1 -1
- package/admin/js/views/user-editor.js +1 -1
- package/admin/js/views/users.js +3 -3
- package/admin/js/views/view-editor.js +1 -1
- package/admin/js/views/view-preview.js +1 -1
- package/admin/js/views/views-list.js +1 -1
- package/bin/cli.js +8 -8
- package/bin/lib/config-merge.js +5 -5
- package/bin/update.js +16 -16
- package/config/site.json +86 -86
- package/package.json +2 -2
- package/plugins/_template/admin/views/index.js +1 -1
- package/plugins/_template/config.js +1 -1
- package/plugins/_template/plugin.js +1 -1
- package/plugins/analytics/admin/templates/analytics.html +3 -3
- package/plugins/analytics/admin/views/analytics.js +1 -1
- package/plugins/analytics/config.js +1 -1
- package/plugins/analytics/plugin.js +4 -4
- package/plugins/analytics/plugin.json +1 -1
- package/plugins/analytics/public/inject-body.html +2 -2
- package/plugins/analytics/public/inject-head.html +1 -1
- package/plugins/blog/admin/templates/blog.html +4 -4
- package/plugins/blog/admin/views/blog.js +3 -3
- package/plugins/blog/admin/views/categories.js +2 -2
- package/plugins/blog/admin/views/comments.js +4 -4
- package/plugins/blog/admin/views/post-editor.js +1 -1
- package/plugins/blog/plugin.js +2 -2
- package/plugins/blog/plugin.json +4 -4
- package/plugins/contacts/admin/views/contacts.js +8 -8
- package/plugins/contacts/plugin.js +4 -4
- package/plugins/contacts/plugin.json +1 -1
- package/plugins/invoice/admin/templates/editor.html +6 -6
- package/plugins/invoice/admin/templates/index.html +4 -4
- package/plugins/invoice/admin/views/editor.js +3 -3
- package/plugins/invoice/admin/views/index.js +4 -4
- package/plugins/invoice/admin/views/issuers.js +1 -1
- package/plugins/invoice/admin/views/party-view.js +3 -3
- package/plugins/invoice/admin/views/receivers.js +1 -1
- package/plugins/invoice/config.js +1 -1
- package/plugins/invoice/plugin.js +1 -1
- package/plugins/invoice/plugin.json +4 -4
- package/plugins/notes/admin/views/notes.js +4 -4
- package/plugins/notes/plugin.js +1 -1
- package/plugins/notes/plugin.json +1 -1
- package/plugins/site-search/admin/templates/site-search.html +3 -3
- package/plugins/site-search/admin/views/site-search.js +1 -1
- package/plugins/site-search/config.js +1 -1
- package/plugins/site-search/plugin.js +4 -4
- package/plugins/site-search/plugin.json +1 -1
- package/plugins/surveys/admin/templates/results.html +1 -1
- package/plugins/surveys/admin/templates/survey-editor.html +4 -4
- package/plugins/surveys/admin/views/audience.js +6 -6
- package/plugins/surveys/admin/views/results.js +3 -3
- package/plugins/surveys/admin/views/survey-editor.js +1 -1
- package/plugins/surveys/admin/views/surveys.js +2 -2
- package/plugins/surveys/plugin.js +1 -1
- package/plugins/surveys/plugin.json +4 -4
- package/plugins/theme-switcher/admin/views/theme-switcher.js +1 -1
- package/plugins/theme-switcher/plugin.json +3 -3
- package/plugins/theme-switcher/public/inject-body.html +28 -28
- package/plugins/theme-switcher/public/inject-head.html +1 -1
- package/plugins/todo/plugin.js +1 -1
- package/public/css/forms.css +1 -1
- package/public/js/collection-browser.js +1 -1
- package/public/js/collection-context.js +1 -1
- package/public/js/form-logic-engine.js +1 -1
- package/public/js/forms.js +2 -1
- package/public/js/site.js +1 -1
- package/scripts/build.js +100 -6
- package/scripts/create-plugin.js +2 -2
- package/scripts/fresh.js +2 -2
- package/scripts/gen-instance-secret.js +1 -1
- package/scripts/pro.js +12 -12
- package/scripts/reset.js +2 -2
- package/scripts/setup.js +15 -15
- package/scripts/users.js +5 -5
- package/scripts/verify-assets.mjs +6 -6
- package/server/config.js +1 -1
- package/server/middleware/auth.js +11 -11
- package/server/middleware/managerAuth.js +3 -3
- package/server/routes/api/actions.js +4 -4
- package/server/routes/api/api-endpoints.js +7 -7
- package/server/routes/api/api-tokens.js +5 -5
- package/server/routes/api/auth.js +10 -10
- package/server/routes/api/blocks.js +3 -3
- package/server/routes/api/collections.js +18 -18
- package/server/routes/api/dashboard.js +5 -5
- package/server/routes/api/effects.js +2 -2
- package/server/routes/api/endpoints-public.js +5 -5
- package/server/routes/api/forms.js +47 -47
- package/server/routes/api/menu-locations.js +3 -3
- package/server/routes/api/menus.js +7 -7
- package/server/routes/api/notifications.js +2 -2
- package/server/routes/api/pages.js +14 -2
- package/server/routes/api/plugin-marketplace.js +3 -3
- package/server/routes/api/plugins.js +6 -6
- package/server/routes/api/projects.js +9 -9
- package/server/routes/api/scaffold.js +7 -7
- package/server/routes/api/settings.js +6 -6
- package/server/routes/api/sidebar.js +1 -1
- package/server/routes/api/users.js +5 -5
- package/server/routes/api/versions.js +1 -1
- package/server/routes/api/views.js +1 -1
- package/server/routes/docs-public.js +3 -3
- package/server/routes/public.js +14 -14
- package/server/server.js +34 -16
- package/server/services/actions.js +19 -19
- package/server/services/adapters/FileAdapter.js +2 -2
- package/server/services/adapters/MongoAdapter.js +5 -5
- package/server/services/apiEndpoints.js +15 -15
- package/server/services/apiTokens.js +7 -7
- package/server/services/blocks.js +13 -13
- package/server/services/cache/drivers/MemoryDriver.js +3 -3
- package/server/services/cache/drivers/NoneDriver.js +1 -1
- package/server/services/cache/index.js +5 -5
- package/server/services/collections.js +14 -14
- package/server/services/components.js +9 -9
- package/server/services/connectionManager.js +4 -4
- package/server/services/content.js +25 -2
- package/server/services/docs.js +14 -14
- package/server/services/email.js +1 -1
- package/server/services/filterEngine.js +6 -6
- package/server/services/forms.js +18 -10
- package/server/services/health.js +1 -1
- package/server/services/hooks.js +7 -7
- package/server/services/images.js +4 -4
- package/server/services/managerClient.js +1 -1
- package/server/services/markdown.js +79 -79
- package/server/services/menuRender.js +5 -5
- package/server/services/menus-migration.js +3 -3
- package/server/services/menus.js +31 -31
- package/server/services/permissionRegistry.js +1 -1
- package/server/services/pluginFiles.js +5 -5
- package/server/services/pluginInstaller.js +17 -17
- package/server/services/pluginScaffold.js +1 -1
- package/server/services/plugins.js +17 -17
- package/server/services/presetCollections.js +2 -2
- package/server/services/projects.js +35 -26
- package/server/services/recipes/contact-list.json +1 -1
- package/server/services/recipes/onboarding.json +8 -8
- package/server/services/references.js +8 -8
- package/server/services/renderer.js +9 -9
- package/server/services/roles.js +9 -9
- package/server/services/rowAccess.js +10 -10
- package/server/services/scaffolder.js +30 -30
- package/server/services/sidebar-migration.js +10 -10
- package/server/services/sitemap.js +1 -1
- package/server/services/userProfiles.js +4 -4
- package/server/services/userRoles.js +7 -7
- package/server/services/users.js +5 -5
- package/server/services/versions.js +3 -3
- package/server/services/viewPipeline.js +6 -6
- package/server/services/views.js +11 -11
- package/server/templates/page.html +5 -5
- package/config/navigation.json.bak +0 -31
package/public/js/site.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{makeBadgeEl as _,applyColour as A,applyPill as I,applyFloat as M}from"/public/js/menu-decor.mjs?v=20260817-menu-bindings";import{enhancePickerFields as D}from"/public/js/forms.js?v=20260817-multi-chooser";$(()=>{const b=window.__CMS_NAV__||{},g=window.__CMS_SITE__||{};if(g.autoTheme?.enabled){let n=function(e){const d=(e||"07:00").split(":");return+d[0]*60+(+d[1]||0)},a=function(){const e=new Date,d=e.getHours()*60+e.getMinutes();return d>=n(t.dayStart)&&d<n(t.nightStart)?t.dayTheme:t.nightTheme};const t=g.autoTheme;Domma.theme.set(a()),setInterval(()=>Domma.theme.set(a()),6e4)}const v=$("#site-navbar");if(v.length&&b.brand){let a=function(s,r=0){return Array.isArray(s)?s.filter(c=>!(c&&c.type==="separator"&&r===0)).map(c=>c&&c.type==="separator"?{divider:!0}:Array.isArray(c?.items)?{...c,items:a(c.items,r+1)}:c):s},e=function(s){return Array.isArray(s)?s.filter(r=>!(r&&r.type==="separator")):[]};const t={...b.brand},n=t.size&&t.size!=="md"?` navbar-brand-${t.size}`:"";if(t.logo||t.icon||t.tagline){let s="";t.logo?s+=`<img src="${t.logo}" class="navbar-brand-logo" alt="${t.text||""}">`:t.icon&&(s+=`<span data-icon="${t.icon}" style="width:1.1em;height:1.1em;margin-right:.35em;vertical-align:middle;"></span>`),t.text&&(s+=`<span class="navbar-brand-text${n}">${t.text}</span>`),t.tagline&&(s+=`<small class="navbar-brand-tagline">${t.tagline}</small>`),t.html=s}else n&&t.text&&(t.html=`<span class="navbar-brand-text${n}">${t.text}</span>`);const d=b.position==="floating";b.orientation==="vertical"&&v.get(0).classList.add("dm-nav-vertical",b.side==="right"?"dm-nav-vertical-right":"dm-nav-vertical-left"),Domma.elements.navbar("#site-navbar",{brand:t,items:a(b.items||[]),variant:b.variant||"dark",position:d?"static":b.position||"sticky",collapsible:!0}),Domma.icons.scan("#site-navbar"),d&&M(v.get(0),b);const h=v.get(0);if(b.appearOnHover){const s=window.matchMedia("(min-width: 993px)");h.querySelectorAll(".navbar-dropdown").forEach(r=>{r.addEventListener("mouseenter",()=>{s.matches&&r.classList.add("open")}),r.addEventListener("mouseleave",()=>{s.matches&&r.classList.remove("open")})})}const m=h.querySelector(".navbar-toggle"),y=h.querySelector(".navbar-collapse");if(m&&y){let s=null;const r=()=>{y.classList.contains("show")&&m.click()},c=()=>{s&&(s.remove(),s=null),document.body.classList.remove("dm-drawer-open")},o=()=>{s||(s=document.createElement("div"),s.className="dm-navbar-backdrop",s.addEventListener("click",r),document.body.appendChild(s),document.body.classList.add("dm-drawer-open"))};m.addEventListener("click",()=>{requestAnimationFrame(()=>{y.classList.contains("show")?o():c()})}),y.addEventListener("click",i=>{i.target.closest("a")&&requestAnimationFrame(c)}),document.addEventListener("keydown",i=>{i.key==="Escape"&&y.classList.contains("show")&&r()})}const l=e(b.items||[]);h.querySelectorAll(".navbar-link[data-index], .navbar-dropdown-item[data-index]").forEach(s=>{const r=l[parseInt(s.dataset.index,10)];if(!r)return;const c=s.dataset.subindex!=null,o=c&&Array.isArray(r.items)?r.items[parseInt(s.dataset.subindex,10)]:r;if(!o)return;o.colour&&A(s,o.colour),o.pill&&!c&&I(s,o.pill);const i=_(o.badge);i&&s.appendChild(i)})}const w=$("#site-footer");if(w.length){let d=function(o){return String(o??"").replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""")},p=function(o){return d(o).replace(/'/g,"'")},h=function(o){if(o&&o.type==="separator")return'<li class="footer-sep"><hr></li>';const i=p(o.url||"#"),f=d(o.text||""),k=Array.isArray(o.items)&&o.items.length?`<ul>${o.items.map(h).join("")}</ul>`:"";return`<li><a href="${i}">${f}</a>${k}</li>`},m=function(o,i){return(o||[]).forEach(f=>{f&&f.type==="separator"||(i.push(f),Array.isArray(f.items)&&f.items.length&&m(f.items,i))}),i};const t=g.social||{},n={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 a='<div class="footer-inner container">';const e=window.__CMS_FOOTER__||{primary:[],legal:[],copyright:""};if(e.copyright&&(a+=`<p>${d(e.copyright)}</p>`),e.primary.length&&(a+=`<nav class="footer-nav footer-nav--primary"><ul>${e.primary.map(h).join("")}</ul></nav>`),e.legal.length&&(a+=`<nav class="footer-nav footer-nav--legal"><ul>${e.legal.map(h).join("")}</ul></nav>`),g.footer){const o=Object.keys(n).filter(i=>t[i]);o.length&&(a+='<div class="footer-social">',o.forEach(i=>{const{label:f,svg:k}=n[i];a+=`<a href="${t[i]}" target="_blank" rel="noopener noreferrer" aria-label="${f}" class="footer-social-link">${k}</a>`}),a+="</div>")}a+="</div>",w.html(a);const y=[...m(e.primary,[]),...m(e.legal,[])];w.get(0).querySelectorAll(".footer-nav a").forEach((o,i)=>{const f=y[i];if(!f)return;f.colour&&A(o,f.colour);const k=_(f.badge);k&&o.appendChild(k)});const s=S.get("reduced_motion"),r=s!==null?!!s:!!(window.matchMedia&&window.matchMedia("(prefers-reduced-motion: reduce)").matches),c=w.get(0).querySelector(".footer-inner");if(c){const o=document.createElement("input");o.type="checkbox",o.className="form-switch-input",o.id="dm-motion-switch",o.checked=r,o.addEventListener("change",function(){S.set("reduced_motion",this.checked),window.location.reload()});const i=document.createElement("span");i.className="form-switch-label",i.textContent="Reduce motion";const f=document.createElement("label");f.className="form-switch footer-motion-switch",f.title="Reduce motion",f.appendChild(o),f.appendChild(i),c.appendChild(f)}}$("#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 u=$(".page-body");if(u.length){u.find(".accordion").each(function(){Domma.elements.accordion(this,{allowMultiple:this.dataset.multi==="true"})}),u.find(".tabs").each(function(){Domma.elements.tabs(this)}),u.find(".carousel").each(function(){const t={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 n=parseInt(this.dataset.animationDuration,10);Number.isFinite(n)&&(t.animationDuration=n)}this.dataset.animationEasing&&(t.animationEasing=this.dataset.animationEasing),this.dataset.ctx&&(this._dmCtxSlides=Array.from(this.querySelectorAll(".carousel-slide")).map(n=>n.cloneNode(!0)),this._dmCarouselOpts=t),this._dmCarousel=Domma.elements.carousel(this,t)}),u.find(".dm-countdown").each(function(){const t={autoStart:!0};this.dataset.to&&(t.targetDate=new Date(this.dataset.to)),this.dataset.duration&&(t.duration=parseInt(this.dataset.duration,10)),this.dataset.format&&(t.format=this.dataset.format),Domma.elements.timer(this,t)}),u.find("[data-tooltip]").each(function(){Domma.elements.tooltip(this,{content:$(this).data("tooltip"),position:$(this).data("tooltip-position")||"top"})}),u.find(".dm-progression").each(function(){const t=Array.from(this.querySelectorAll(".dm-progression-item")).map(e=>{const d={title:e.querySelector(".dm-progression-item-title")?.textContent?.trim()||"",description:e.querySelector(".dm-progression-item-body")?.innerHTML?.trim()||""};return e.dataset.date&&(d.date=e.dataset.date),e.dataset.status&&(d.status=e.dataset.status),e.dataset.entryId&&(d._entryId=e.dataset.entryId),d}),n=this.dataset.layout==="centred"?"centered":this.dataset.layout||"vertical",a=Domma.elements.progression(this,{layout:n,theme:this.dataset.theme||"minimal",mode:this.dataset.mode||"timeline",statusIcons:!0,allowHtml:!0,...t.length?{items:t}:{}});t.length&&(this._dmCtx={instance:a,items:t})});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(t=>{const n=t.dataset.revealMode||"stagger",a=t.dataset.revealAnimation||"slide-up",e=parseInt(t.dataset.revealDuration,10)||400,d=parseInt(t.dataset.revealStagger,10)||60,p=parseInt(t.dataset.revealDelay,10)||0,h=t.dataset.revealDirection==="rtl",m=Array.from(t.children),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)"};m.forEach((l,s)=>{l.style.opacity="0",l.style.transform=y[a]||"",l.style.transition=`opacity ${e}ms ease, transform ${e}ms ease`;const r=h?m.length-1-s:s;l.style.transitionDelay=n==="stagger"?`${p+r*d}ms`:`${p}ms`}),requestAnimationFrame(()=>requestAnimationFrame(()=>{const l=new IntersectionObserver(s=>{s.forEach(r=>{r.isIntersecting&&(r.target.offsetWidth,r.target.style.opacity="1",r.target.style.transform="none",l.unobserve(r.target))})},{threshold:.1});m.forEach(s=>l.observe(s))}))}),u.find(".card[data-collapsible]").each(function(){const t=this.querySelector(".card-header");t&&t.addEventListener("click",()=>this.classList.toggle("is-collapsed"))}),u.find(".dm-so-trigger").each(function(){this.addEventListener("click",()=>{const t=this.dataset.soTarget,n=document.getElementById(t);if(!n)return;const a=E.slideover({title:n.dataset.soTitle||"",size:n.dataset.soSize||"md",position:n.dataset.soPosition||"right"});n.style.display="",a.setContent(n),a.open()})})}if(typeof $.setup=="function"){const t=Object.assign({},window.__CMS_DCONFIG__||{});if(document.querySelectorAll(".dm-page-config[data-config]").forEach(n=>{try{const a=atob(n.dataset.config),e=JSON.parse(a);Object.assign(t,e)}catch{}}),Object.keys(t).length>0){const n={};for(const[a,e]of Object.entries(t)){const d=e?.events?.click,{confirm:p,toast:h,alert:m,prompt:y,...l}=d||{};p||h||m||y?($(a).on("click",async function(r){if(r.preventDefault(),p&&!await E.confirm(p))return;let c=null;if(!(y&&(c=await E.prompt(y,{inputPlaceholder:l.promptPlaceholder||"",inputValue:l.promptDefault||""}),c===null))){if(l.target){const o=$(l.target);l.toggleClass&&o.toggleClass(l.toggleClass),l.addClass&&o.addClass(l.addClass),l.removeClass&&o.removeClass(l.removeClass),c!==null&&(l.setText&&o.text(c),l.setVal&&o.val(c),l.setAttr&&o.attr(l.setAttr,c))}l.href&&(window.location.href=l.href),h&&E.toast(h,{type:l.toastType||"success"}),m&&E.alert(m)}}),Object.keys(l).length&&(n[a]={...e,events:{...e.events,click:l}})):n[a]=e}$.setup(n)}}u.length&&x(u.get(0))});function x(b){b.querySelectorAll(".dm-cta-trigger").forEach(g=>{g.__ctaWired||(g.__ctaWired=!0,g.addEventListener("click",async()=>{const v=g.dataset.action,w=g.dataset.entry,C=g.dataset.confirm;let u=S.get("auth_token");if(!u){E.toast("Please log in to perform this action.",{type:"warning"});return}if(C&&!await E.confirm(C))return;const t=Array.from(g.childNodes).map(a=>a.cloneNode(!0));g.disabled=!0,g.textContent="Running\u2026";const n=a=>fetch(`/api/actions/${v}/public`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${a}`},body:JSON.stringify({entryId:w})});try{let a=await n(u);if(a.status===401){const d=S.get("auth_refresh_token");if(d){const p=await fetch("/api/auth/refresh",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({refreshToken:d})});if(p.ok){const{token:h}=await p.json();S.set("auth_token",h),u=h,a=await n(u)}}}const e=await a.json().catch(()=>({}));if(!a.ok)throw new Error(e.error||e.message||`Error ${a.status}`);E.toast(e.message||"Action completed.",{type:"success"})}catch(a){E.toast(a.message||"Action failed.",{type:"error"})}finally{g.disabled=!1,g.textContent="",t.forEach(a=>g.appendChild(a)),Domma.icons.scan(g)}}))})}(function(){const g=document.querySelectorAll('.dm-collection-hydrate[data-collection-scope="mine"]');if(!g.length)return;const v=S.get("auth_token");function w(u,t){const n=document.createElement("div");n.className="dm-collection-display dm-collection-empty";const a=document.createElement("p");if(t){a.appendChild(document.createTextNode("Please "));const e=document.createElement("a");e.href="/login",e.textContent="sign in",a.appendChild(e),a.appendChild(document.createTextNode(" to view your entries."))}else a.textContent=u;return n.appendChild(a),n}function C(u,t){const n=document.createElement("template");n.innerHTML=t||"",u.replaceWith(n.content)}g.forEach(async u=>{const t=u.dataset.collectionAttrs;if(!t)return;if(!v){u.replaceChildren(w("",!0));return}const n=a=>fetch("/api/collections/render-scope",{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${a}`},body:JSON.stringify({attrs:t})});try{let a=v,e=await n(a);if(e.status===401){const p=S.get("auth_refresh_token");if(p){const h=await fetch("/api/auth/refresh",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({refreshToken:p})});if(h.ok){const{token:m}=await h.json();S.set("auth_token",m),a=m,e=await n(a)}}}if(!e.ok){const{error:p}=await e.json().catch(()=>({}));u.replaceChildren(w(p||"Unable to load entries.",!1));return}const{html:d}=await e.json();C(u,d)}catch{u.replaceChildren(w("Unable to load entries.",!1))}}),setTimeout(()=>{typeof Domma<"u"&&Domma.icons?.scan&&Domma.icons.scan(),typeof Domma<"u"&&Domma.syntax?.scan&&Domma.syntax.scan(),x(document.body)},100)})(),(function(){const g=document.querySelectorAll("[data-collection-table]");!g.length||typeof T>"u"||!T.create||g.forEach(v=>{let w;try{w=JSON.parse(atob(v.dataset.payload))}catch{return}const{columns:C,rows:u,search:t,sortable:n,exportable:a,pageSize:e,empty:d,ctaConfig:p,slug:h,exportCfg:m}=w;if(!C?.length)return;if(p){const s=C.findIndex(r=>r.key==="_cta");s!==-1&&(C[s]={key:"_cta",title:"",render:(r,c)=>{const o=document.createElement("button");if(o.className=`btn btn-${p.style||"primary"} dm-cta-trigger`,o.dataset.action=p.action||"",o.dataset.entry=c._entryId||"",p.confirm&&(o.dataset.confirm=p.confirm),p.icon){const i=document.createElement("span");i.dataset.icon=p.icon,o.appendChild(i),o.appendChild(document.createTextNode(" "))}return o.appendChild(document.createTextNode(p.label||"Run")),o}})}const y="col-table-"+Math.random().toString(36).slice(2,7),l=document.createElement("div");l.id=y,v.replaceChildren(l),T.create("#"+y,{data:u,columns:C,search:t,sortable:n,exportable:a,pageSize:e,emptyMessage:d}),p&&x(l),v._dmTable={columns:C,rows:u,slug:h,exportCfg:m,render(s){T.create("#"+y,{data:s,columns:C,search:t,sortable:n,exportable:a,pageSize:e,emptyMessage:d}),p&&x(l)}}})})(),(function(){const g=document.querySelectorAll("[data-form-inline]");if(!g.length||typeof F>"u")return;function v(t,n){const a={};return(t||[]).forEach(e=>{if(e.type==="page-break"||e.type==="spacer"||!e.name)return;const d=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":d?"chooser":e.type,h={...e.formConfig||{}};h.span==="full"&&n&&(h.span=n);const m={type:p,label:e.label,required:e.required,options:e.options,formConfig:{...e.placeholder&&{placeholder:e.placeholder},...e.helper&&{hint:e.helper},...h}};d&&(e.variant&&(m.variant=e.variant),(e.multiple||e.type==="checkbox-group")&&(m.multiple=!0),e.density&&(m.density=e.density),e.columns&&(m.columns=e.columns),e.accent&&(m.accent=e.accent),e.accentStyle&&(m.accentStyle=e.accentStyle),e.glow&&(m.glow=!0),e.glowColour&&(m.glowColour=e.glowColour),e.shadow&&(m.shadow=e.shadow),e.shadowColour&&(m.shadowColour=e.shadowColour)),a[e.name]=m}),a}function w(t){const n={};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&&(n[a.name]=typeof e=="object"?e.value:e)}}),n}function C(t,n){(n||[]).forEach(a=>{if(a.type!=="date"||!a.name)return;const e=t.querySelector(`[name="${a.name}"]`);e&&e.type!=="date"&&(e.type="date")})}function u(t,n,a){let e=t.querySelector(".cms-form-message");e||(e=document.createElement("p"),e.className="cms-form-message",t.appendChild(e)),e.textContent=n,e.style.cssText=a?"color:var(--dm-danger);margin-top:.75rem;":"color:var(--dm-success);margin-top:.75rem;"}g.forEach(t=>{let n;try{n=JSON.parse(atob(t.dataset.formInline))}catch{return}const a=n.fields||[],e=n.settings||{},d=e.columns||1,p=e.layout||"stacked",h=a.some(s=>s.type==="page-break"),m=async s=>{try{const r=t.querySelector('[name="website"]'),c=t.querySelector('[name="_t"]'),o=Object.assign({},s);r!==null&&(o._hp=r.value),c!==null&&(o._t=c.value);const i=await H.post(`/api/forms/submit/${n.slug}`,o);if(i&&i.error){u(t,i.error,!0);const f=new Error(i.error);throw f.formSubmitFailed=!0,f}if(i?.redirect){window.location.href=i.redirect;return}for(;t.firstChild;)t.removeChild(t.firstChild);u(t,i?.message||e.successMessage||"Thank you for your submission.",!1)}catch(r){throw r.formSubmitFailed||u(t,r.message||"Submission failed. Please try again.",!0),r}};function y(s){const r=s.querySelector("form");if(!r)return;const c=document.createElement("div");c.className="fb-form-honeypot",c.setAttribute("aria-hidden","true");const o=document.createElement("input");o.name="website",o.type="text",o.tabIndex=-1,o.autocomplete="url",o.placeholder="https://",c.appendChild(o);const i=document.createElement("input");i.name="_t",i.type="hidden",i.value=Date.now(),c.appendChild(i),r.appendChild(c)}function l(){if(!window.FormLogicEngine||!a.some(r=>r.logic))return;const s=new window.FormLogicEngine.FormLogicRuntime(n,t);if(s.init(),t._formLogicRuntime=s,t.parentNode&&typeof MutationObserver<"u"){const r=new MutationObserver(function(c){for(const o of c)for(const i of o.removedNodes)if(i===t||i.nodeType===1&&i.contains&&i.contains(t)){s.destroy(),r.disconnect();return}});r.observe(t.parentNode,{childList:!0,subtree:!1})}}if(h&&F.wizard){const s=[];let r=[],c=n.title||"Step 1",o="";a.forEach(f=>{f.type==="page-break"?(s.push({title:c,description:o,fields:v(r,d)}),r=[],c=f.label||`Step ${s.length+1}`,o=f.description||""):f.type!=="spacer"&&r.push(f)}),s.push({title:c,description:o,fields:v(r,d)});const i=F.wizard(t,{schema:{steps:s},onSubmit:m});Promise.resolve(i).then(function(){C(t,a),D(t,a),e.honeypot!==!1&&y(t),l()})}else if(F.render){const s=F.render(t,v(a,d),w(a),{submitText:e.submitText||"Submit",layout:p,columns:d,onSubmit:m});Promise.resolve(s).then(function(){if(p==="grid"&&e.submitSpan==="full"){const r=t.querySelector(".form-buttons");r&&r.classList.add("col-span-full")}C(t,a),D(t,a),e.honeypot!==!1&&y(t),l()})}}),$(document).on("click",".dm-banner__dismiss",function(){$(this).closest(".dm-banner").remove()})})();
|
|
1
|
+
import{makeBadgeEl as M,applyColour as O,applyPill as R,applyFloat as P}from"/public/js/menu-decor.mjs?v=20260817-menu-bindings";import{enhancePickerFields as z,markInvalidFields as B,showValidationPopup as V}from"/public/js/forms.js?v=20260821-wizard-steps";$(()=>{const v=window.__CMS_NAV__||{},h=window.__CMS_SITE__||{};if(h.autoTheme?.enabled){let t=function(a){const e=(a||"07:00").split(":");return+e[0]*60+(+e[1]||0)},o=function(){const a=new Date,e=a.getHours()*60+a.getMinutes();return e>=t(n.dayStart)&&e<t(n.nightStart)?n.dayTheme:n.nightTheme};const n=h.autoTheme;Domma.theme.set(o()),setInterval(()=>Domma.theme.set(o()),6e4)}const w=$("#site-navbar");if(w.length&&v.brand){let o=function(r,i=0){return Array.isArray(r)?r.filter(c=>!(c&&c.type==="separator"&&i===0)).map(c=>c&&c.type==="separator"?{divider:!0}:Array.isArray(c?.items)?{...c,items:o(c.items,i+1)}:c):r},a=function(r){return Array.isArray(r)?r.filter(i=>!(i&&i.type==="separator")):[]};const n={...v.brand},t=n.size&&n.size!=="md"?` navbar-brand-${n.size}`:"";if(n.logo||n.icon||n.tagline){let r="";n.logo?r+=`<img src="${n.logo}" class="navbar-brand-logo" alt="${n.text||""}">`:n.icon&&(r+=`<span data-icon="${n.icon}" style="width:1.1em;height:1.1em;margin-right:.35em;vertical-align:middle;"></span>`),n.text&&(r+=`<span class="navbar-brand-text${t}">${n.text}</span>`),n.tagline&&(r+=`<small class="navbar-brand-tagline">${n.tagline}</small>`),n.html=r}else t&&n.text&&(n.html=`<span class="navbar-brand-text${t}">${n.text}</span>`);const e=v.position==="floating";v.orientation==="vertical"&&w.get(0).classList.add("dm-nav-vertical",v.side==="right"?"dm-nav-vertical-right":"dm-nav-vertical-left"),Domma.elements.navbar("#site-navbar",{brand:n,items:o(v.items||[]),variant:v.variant||"dark",position:e?"static":v.position||"sticky",collapsible:!0}),Domma.icons.scan("#site-navbar"),e&&P(w.get(0),v);const g=w.get(0);if(v.appearOnHover){const r=window.matchMedia("(min-width: 993px)");g.querySelectorAll(".navbar-dropdown").forEach(i=>{i.addEventListener("mouseenter",()=>{r.matches&&i.classList.add("open")}),i.addEventListener("mouseleave",()=>{r.matches&&i.classList.remove("open")})})}const y=g.querySelector(".navbar-toggle"),f=g.querySelector(".navbar-collapse");if(y&&f){let r=null;const i=()=>{f.classList.contains("show")&&y.click()},c=()=>{r&&(r.remove(),r=null),document.body.classList.remove("dm-drawer-open")},s=()=>{r||(r=document.createElement("div"),r.className="dm-navbar-backdrop",r.addEventListener("click",i),document.body.appendChild(r),document.body.classList.add("dm-drawer-open"))};y.addEventListener("click",()=>{requestAnimationFrame(()=>{f.classList.contains("show")?s():c()})}),f.addEventListener("click",l=>{l.target.closest("a")&&requestAnimationFrame(c)}),document.addEventListener("keydown",l=>{l.key==="Escape"&&f.classList.contains("show")&&i()})}const m=a(v.items||[]);g.querySelectorAll(".navbar-link[data-index], .navbar-dropdown-item[data-index]").forEach(r=>{const i=m[parseInt(r.dataset.index,10)];if(!i)return;const c=r.dataset.subindex!=null,s=c&&Array.isArray(i.items)?i.items[parseInt(r.dataset.subindex,10)]:i;if(!s)return;s.colour&&O(r,s.colour),s.pill&&!c&&R(r,s.pill);const l=M(s.badge);l&&r.appendChild(l)})}const b=$("#site-footer");if(b.length){let e=function(s){return String(s??"").replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""")},u=function(s){return e(s).replace(/'/g,"'")},g=function(s){if(s&&s.type==="separator")return'<li class="footer-sep"><hr></li>';const l=u(s.url||"#"),d=e(s.text||""),k=Array.isArray(s.items)&&s.items.length?`<ul>${s.items.map(g).join("")}</ul>`:"";return`<li><a href="${l}">${d}</a>${k}</li>`},y=function(s,l){return(s||[]).forEach(d=>{d&&d.type==="separator"||(l.push(d),Array.isArray(d.items)&&d.items.length&&y(d.items,l))}),l};const n=h.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 o='<div class="footer-inner container">';const a=window.__CMS_FOOTER__||{primary:[],legal:[],copyright:""};if(a.copyright&&(o+=`<p>${e(a.copyright)}</p>`),a.primary.length&&(o+=`<nav class="footer-nav footer-nav--primary"><ul>${a.primary.map(g).join("")}</ul></nav>`),a.legal.length&&(o+=`<nav class="footer-nav footer-nav--legal"><ul>${a.legal.map(g).join("")}</ul></nav>`),h.footer){const s=Object.keys(t).filter(l=>n[l]);s.length&&(o+='<div class="footer-social">',s.forEach(l=>{const{label:d,svg:k}=t[l];o+=`<a href="${n[l]}" target="_blank" rel="noopener noreferrer" aria-label="${d}" class="footer-social-link">${k}</a>`}),o+="</div>")}o+="</div>",b.html(o);const f=[...y(a.primary,[]),...y(a.legal,[])];b.get(0).querySelectorAll(".footer-nav a").forEach((s,l)=>{const d=f[l];if(!d)return;d.colour&&O(s,d.colour);const k=M(d.badge);k&&s.appendChild(k)});const r=S.get("reduced_motion"),i=r!==null?!!r:!!(window.matchMedia&&window.matchMedia("(prefers-reduced-motion: reduce)").matches),c=b.get(0).querySelector(".footer-inner");if(c){const s=document.createElement("input");s.type="checkbox",s.className="form-switch-input",s.id="dm-motion-switch",s.checked=i,s.addEventListener("change",function(){S.set("reduced_motion",this.checked),window.location.reload()});const l=document.createElement("span");l.className="form-switch-label",l.textContent="Reduce motion";const d=document.createElement("label");d.className="form-switch footer-motion-switch",d.title="Reduce motion",d.appendChild(s),d.appendChild(l),c.appendChild(d)}}$("#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 p=$(".page-body");if(p.length){p.find(".accordion").each(function(){Domma.elements.accordion(this,{allowMultiple:this.dataset.multi==="true"})}),p.find(".tabs").each(function(){Domma.elements.tabs(this)}),p.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)}),p.find(".dm-countdown").each(function(){const n={autoStart:!0};this.dataset.to&&(n.targetDate=new Date(this.dataset.to)),this.dataset.duration&&(n.duration=parseInt(this.dataset.duration,10)),this.dataset.format&&(n.format=this.dataset.format),Domma.elements.timer(this,n)}),p.find("[data-tooltip]").each(function(){Domma.elements.tooltip(this,{content:$(this).data("tooltip"),position:$(this).data("tooltip-position")||"top"})}),p.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",o=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:o,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",o=n.dataset.revealAnimation||"slide-up",a=parseInt(n.dataset.revealDuration,10)||400,e=parseInt(n.dataset.revealStagger,10)||60,u=parseInt(n.dataset.revealDelay,10)||0,g=n.dataset.revealDirection==="rtl",y=Array.from(n.children),f={"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,r)=>{m.style.opacity="0",m.style.transform=f[o]||"",m.style.transition=`opacity ${a}ms ease, transform ${a}ms ease`;const i=g?y.length-1-r:r;m.style.transitionDelay=t==="stagger"?`${u+i*e}ms`:`${u}ms`}),requestAnimationFrame(()=>requestAnimationFrame(()=>{const m=new IntersectionObserver(r=>{r.forEach(i=>{i.isIntersecting&&(i.target.offsetWidth,i.target.style.opacity="1",i.target.style.transform="none",m.unobserve(i.target))})},{threshold:.1});y.forEach(r=>m.observe(r))}))}),p.find(".card[data-collapsible]").each(function(){const n=this.querySelector(".card-header");n&&n.addEventListener("click",()=>this.classList.toggle("is-collapsed"))}),p.find(".dm-so-trigger").each(function(){this.addEventListener("click",()=>{const n=this.dataset.soTarget,t=document.getElementById(n);if(!t)return;const o=E.slideover({title:t.dataset.soTitle||"",size:t.dataset.soSize||"md",position:t.dataset.soPosition||"right"});t.style.display="",o.setContent(t),o.open()})})}if(typeof $.setup=="function"){const n=Object.assign({},window.__CMS_DCONFIG__||{});if(document.querySelectorAll(".dm-page-config[data-config]").forEach(t=>{try{const o=atob(t.dataset.config),a=JSON.parse(o);Object.assign(n,a)}catch{}}),Object.keys(n).length>0){const t={};for(const[o,a]of Object.entries(n)){const e=a?.events?.click,{confirm:u,toast:g,alert:y,prompt:f,...m}=e||{};u||g||y||f?($(o).on("click",async function(i){if(i.preventDefault(),u&&!await E.confirm(u))return;let c=null;if(!(f&&(c=await E.prompt(f,{inputPlaceholder:m.promptPlaceholder||"",inputValue:m.promptDefault||""}),c===null))){if(m.target){const s=$(m.target);m.toggleClass&&s.toggleClass(m.toggleClass),m.addClass&&s.addClass(m.addClass),m.removeClass&&s.removeClass(m.removeClass),c!==null&&(m.setText&&s.text(c),m.setVal&&s.val(c),m.setAttr&&s.attr(m.setAttr,c))}m.href&&(window.location.href=m.href),g&&E.toast(g,{type:m.toastType||"success"}),y&&E.alert(y)}}),Object.keys(m).length&&(t[o]={...a,events:{...a.events,click:m}})):t[o]=a}$.setup(t)}}p.length&&_(p.get(0))});function _(v){v.querySelectorAll(".dm-cta-trigger").forEach(h=>{h.__ctaWired||(h.__ctaWired=!0,h.addEventListener("click",async()=>{const w=h.dataset.action,b=h.dataset.entry,C=h.dataset.confirm;let p=S.get("auth_token");if(!p){E.toast("Please log in to perform this action.",{type:"warning"});return}if(C&&!await E.confirm(C))return;const n=Array.from(h.childNodes).map(o=>o.cloneNode(!0));h.disabled=!0,h.textContent="Running\u2026";const t=o=>fetch(`/api/actions/${w}/public`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${o}`},body:JSON.stringify({entryId:b})});try{let o=await t(p);if(o.status===401){const e=S.get("auth_refresh_token");if(e){const u=await fetch("/api/auth/refresh",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({refreshToken:e})});if(u.ok){const{token:g}=await u.json();S.set("auth_token",g),p=g,o=await t(p)}}}const a=await o.json().catch(()=>({}));if(!o.ok)throw new Error(a.error||a.message||`Error ${o.status}`);E.toast(a.message||"Action completed.",{type:"success"})}catch(o){E.toast(o.message||"Action failed.",{type:"error"})}finally{h.disabled=!1,h.textContent="",n.forEach(o=>h.appendChild(o)),Domma.icons.scan(h)}}))})}(function(){const h=document.querySelectorAll('.dm-collection-hydrate[data-collection-scope="mine"]');if(!h.length)return;const w=S.get("auth_token");function b(p,n){const t=document.createElement("div");t.className="dm-collection-display dm-collection-empty";const o=document.createElement("p");if(n){o.appendChild(document.createTextNode("Please "));const a=document.createElement("a");a.href="/login",a.textContent="sign in",o.appendChild(a),o.appendChild(document.createTextNode(" to view your entries."))}else o.textContent=p;return t.appendChild(o),t}function C(p,n){const t=document.createElement("template");t.innerHTML=n||"",p.replaceWith(t.content)}h.forEach(async p=>{const n=p.dataset.collectionAttrs;if(!n)return;if(!w){p.replaceChildren(b("",!0));return}const t=o=>fetch("/api/collections/render-scope",{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${o}`},body:JSON.stringify({attrs:n})});try{let o=w,a=await t(o);if(a.status===401){const u=S.get("auth_refresh_token");if(u){const g=await fetch("/api/auth/refresh",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({refreshToken:u})});if(g.ok){const{token:y}=await g.json();S.set("auth_token",y),o=y,a=await t(o)}}}if(!a.ok){const{error:u}=await a.json().catch(()=>({}));p.replaceChildren(b(u||"Unable to load entries.",!1));return}const{html:e}=await a.json();C(p,e)}catch{p.replaceChildren(b("Unable to load entries.",!1))}}),setTimeout(()=>{typeof Domma<"u"&&Domma.icons?.scan&&Domma.icons.scan(),typeof Domma<"u"&&Domma.syntax?.scan&&Domma.syntax.scan(),_(document.body)},100)})(),(function(){const h=document.querySelectorAll("[data-collection-table]");!h.length||typeof T>"u"||!T.create||h.forEach(w=>{let b;try{b=JSON.parse(atob(w.dataset.payload))}catch{return}const{columns:C,rows:p,search:n,sortable:t,exportable:o,pageSize:a,empty:e,ctaConfig:u,slug:g,exportCfg:y}=b;if(!C?.length)return;if(u){const r=C.findIndex(i=>i.key==="_cta");r!==-1&&(C[r]={key:"_cta",title:"",render:(i,c)=>{const s=document.createElement("button");if(s.className=`btn btn-${u.style||"primary"} dm-cta-trigger`,s.dataset.action=u.action||"",s.dataset.entry=c._entryId||"",u.confirm&&(s.dataset.confirm=u.confirm),u.icon){const l=document.createElement("span");l.dataset.icon=u.icon,s.appendChild(l),s.appendChild(document.createTextNode(" "))}return s.appendChild(document.createTextNode(u.label||"Run")),s}})}const f="col-table-"+Math.random().toString(36).slice(2,7),m=document.createElement("div");m.id=f,w.replaceChildren(m),T.create("#"+f,{data:p,columns:C,search:n,sortable:t,exportable:o,pageSize:a,emptyMessage:e}),u&&_(m),w._dmTable={columns:C,rows:p,slug:g,exportCfg:y,render(r){T.create("#"+f,{data:r,columns:C,search:n,sortable:t,exportable:o,pageSize:a,emptyMessage:e}),u&&_(m)}}})})(),(function(){const h=document.querySelectorAll("[data-form-inline]");if(!h.length||typeof F>"u")return;function w(t){const o=t&&t.logic;return!!(o&&(o.visibility||o.requirement))}function b(t,o){const a={};return(t||[]).forEach(e=>{if(e.type==="page-break"||e.type==="spacer"||!e.name)return;const u=e.type==="chooser"||!!(e.variant&&["select","radio","checkbox-group"].includes(e.type)),g=e.type==="checkbox"?"boolean":e.type==="date"||e.type==="icon"||e.type==="image"?"string":u?"chooser":e.type,y={...e.formConfig||{}};y.span==="full"&&o&&(y.span=o);const f={type:g,required:w(e)?!1:e.required,label:e.label,options:e.options,formConfig:{...e.placeholder&&{placeholder:e.placeholder},...e.helper&&{hint:e.helper},...y}};u&&(e.variant&&(f.variant=e.variant),(e.multiple||e.type==="checkbox-group")&&(f.multiple=!0),e.density&&(f.density=e.density),e.columns&&(f.columns=e.columns),e.accent&&(f.accent=e.accent),e.accentStyle&&(f.accentStyle=e.accentStyle),e.glow&&(f.glow=!0),e.glowColour&&(f.glowColour=e.glowColour),e.shadow&&(f.shadow=e.shadow),e.shadowColour&&(f.shadowColour=e.shadowColour)),a[e.name]=f}),a}function C(t){const o={};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&&(o[a.name]=typeof e=="object"?e.value:e)}}),o}function p(t,o){(o||[]).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,o,a){let e=t.querySelector(".cms-form-message");e||(e=document.createElement("p"),e.className="cms-form-message",t.appendChild(e)),e.textContent=o,e.style.cssText=a?"color:var(--dm-danger);margin-top:.75rem;":"color:var(--dm-success);margin-top:.75rem;"}h.forEach(t=>{let o;try{o=JSON.parse(atob(t.dataset.formInline))}catch{return}const a=o.fields||[],e=o.settings||{},u=e.columns||1,g=e.layout||"stacked",y=a.some(i=>i.type==="page-break"),f=async i=>{try{const c=t.querySelector('[name="website"]'),s=t.querySelector('[name="_t"]'),l=Object.assign({},i);c!==null&&(l._hp=c.value),s!==null&&(l._t=s.value);const d=await H.post(`/api/forms/submit/${o.slug}`,l);if(d&&d.error){n(t,d.error,!0);const k=new Error(d.error);throw k.formSubmitFailed=!0,k}if(d?.redirect){window.location.href=d.redirect;return}for(;t.firstChild;)t.removeChild(t.firstChild);n(t,d?.message||e.successMessage||"Thank you for your submission.",!1)}catch(c){throw c.formSubmitFailed||n(t,c.message||"Submission failed. Please try again.",!0),c}};function m(i){const c=i.querySelector("form");if(!c)return;const s=document.createElement("div");s.className="fb-form-honeypot",s.setAttribute("aria-hidden","true");const l=document.createElement("input");l.name="website",l.type="text",l.tabIndex=-1,l.autocomplete="url",l.placeholder="https://",s.appendChild(l);const d=document.createElement("input");d.name="_t",d.type="hidden",d.value=Date.now(),s.appendChild(d),c.appendChild(s)}function r(){if(!window.FormLogicEngine||!a.some(c=>c.logic))return null;const i=new window.FormLogicEngine.FormLogicRuntime(o,t);if(i.init(),t._formLogicRuntime=i,t._formLogicObserved)return i;if(t._formLogicObserved=!0,t.parentNode&&typeof MutationObserver<"u"){const c=new MutationObserver(function(s){for(const l of s)for(const d of l.removedNodes)if(d===t||d.nodeType===1&&d.contains&&d.contains(t)){t._formLogicRuntime&&t._formLogicRuntime.destroy(),c.disconnect();return}});c.observe(t.parentNode,{childList:!0,subtree:!1})}return i}if(y&&F.wizard){const i=[],c=[];let s=[],l=o.title||"Step 1",d="";const k=()=>{i.push({title:l,description:d,fields:b(s,u)}),c.push(s)};a.forEach(x=>{x.type==="page-break"?(k(),s=[],l=x.label||`Step ${i.length+1}`,d=x.description||""):x.type!=="spacer"&&s.push(x)}),k();let L=0,A=null;const N=x=>{const I=window.FormLogicEngine;if(!I||!I.validateFields)return;const D=I.validateFields(t,c[L]||[]);D.valid||(x.preventDefault(),x.stopImmediatePropagation(),B(t,D.missing),V(D))},q=()=>{p(t,a),z(t,a),e.honeypot!==!1&&m(t),A&&A.destroy(),A=r(),t.querySelectorAll(".wizard-next, .wizard-submit").forEach(x=>x.addEventListener("click",N,!0))},j=F.wizard(t,{schema:{steps:i},onSubmit:f,onStepChange:x=>{L=x,setTimeout(q,0)}});Promise.resolve(j).then(q)}else if(F.render){const i=F.render(t,b(a,u),C(a),{submitText:e.submitText||"Submit",layout:g,columns:u,onSubmit:f});Promise.resolve(i).then(function(){if(g==="grid"&&e.submitSpan==="full"){const c=t.querySelector(".form-buttons");c&&c.classList.add("col-span-full")}p(t,a),z(t,a),e.honeypot!==!1&&m(t),r()})}}),$(document).on("click",".dm-banner__dismiss",function(){$(this).closest(".dm-banner").remove()})})();
|
package/scripts/build.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Build script
|
|
2
|
+
* Build script - creates a publishable staging copy in _publish/
|
|
3
3
|
* Browser-facing JS/CSS is minified; server-side code is copied as-is.
|
|
4
4
|
* Run: node scripts/build.js
|
|
5
5
|
*/
|
|
@@ -29,7 +29,7 @@ const COPY_AS_IS = [
|
|
|
29
29
|
];
|
|
30
30
|
|
|
31
31
|
// Glob patterns for browser-facing assets to minify. NOTE: both .js AND .mjs
|
|
32
|
-
// must be listed
|
|
32
|
+
// must be listed - the browser ESM modules served raw (e.g. public/js/*.mjs)
|
|
33
33
|
// use the .mjs extension, and omitting it silently drops those files from the
|
|
34
34
|
// published package (this is the bug that shipped 0.22.1 without menu-decor.mjs).
|
|
35
35
|
const MINIFY_PATTERNS = [
|
|
@@ -169,6 +169,60 @@ async function stampPluginVersions() {
|
|
|
169
169
|
}
|
|
170
170
|
}
|
|
171
171
|
|
|
172
|
+
/**
|
|
173
|
+
* Domma Tools is the one Domma asset npm cannot deliver.
|
|
174
|
+
*
|
|
175
|
+
* `domma-js` does not list `domma-tools.*` in its package `files`, so the pair under
|
|
176
|
+
* `admin/dist/domma/` can only ever arrive by hand-copying it out of a domma build. Meanwhile
|
|
177
|
+
* `/dist/domma/domma.min.js` is served straight from `node_modules/domma-js` (server.js), so a
|
|
178
|
+
* `npm install domma-js@next` silently upgrades one half of the admin and leaves the other on
|
|
179
|
+
* whatever version was last copied across. The tools bundle's own banner says
|
|
180
|
+
* `Requires: domma.min.js` - it is built against a specific core and is not version-independent.
|
|
181
|
+
*
|
|
182
|
+
* That skew is invisible: both files load, no import dangles, nothing throws. It surfaces later as
|
|
183
|
+
* a Theme Roller or Page Roller that misbehaves against a core it was not built for. This is the
|
|
184
|
+
* one place that can see both versions at once, so it is the place that checks.
|
|
185
|
+
*
|
|
186
|
+
* @returns {{ok: boolean, installed: string, found: Array<{rel: string, version: string|null}>, problems: string[]}}
|
|
187
|
+
*/
|
|
188
|
+
function verifyDommaToolsVersion(toolsRelPaths) {
|
|
189
|
+
const installed = JSON.parse(
|
|
190
|
+
readFileSync(join(ROOT, 'node_modules/domma-js/package.json'), 'utf8'),
|
|
191
|
+
).version;
|
|
192
|
+
|
|
193
|
+
// Banners differ between the two artefacts: "Domma Tools v0.38.0" (JS) and
|
|
194
|
+
// "Domma Tools CSS v0.38.0" (CSS). Match both rather than only the one you happened to open.
|
|
195
|
+
const BANNER = /\*\s*Domma Tools(?: CSS)? v(\d+\.\d+\.\d+)/;
|
|
196
|
+
|
|
197
|
+
const found = [];
|
|
198
|
+
const problems = [];
|
|
199
|
+
|
|
200
|
+
for (const rel of toolsRelPaths) {
|
|
201
|
+
const src = join(ROOT, rel);
|
|
202
|
+
if (!existsSync(src)) {
|
|
203
|
+
// admin/index.html loads both unconditionally, so an absent file is a dead <script>/<link>
|
|
204
|
+
// in the published admin. The copy step already tolerates absence; flag it rather than
|
|
205
|
+
// silently agreeing.
|
|
206
|
+
problems.push(`${rel} is missing - the published admin will reference a file that isn't there`);
|
|
207
|
+
continue;
|
|
208
|
+
}
|
|
209
|
+
// The banner is in the first few lines; no need to read a 230 KB bundle in full.
|
|
210
|
+
const head = readFileSync(src, 'utf8').slice(0, 512);
|
|
211
|
+
const match = head.match(BANNER);
|
|
212
|
+
if (!match) {
|
|
213
|
+
problems.push(`${rel} carries no recognisable Domma Tools banner - cannot confirm its version`);
|
|
214
|
+
found.push({rel, version: null});
|
|
215
|
+
continue;
|
|
216
|
+
}
|
|
217
|
+
found.push({rel, version: match[1]});
|
|
218
|
+
if (match[1] !== installed) {
|
|
219
|
+
problems.push(`${rel} is v${match[1]} but domma-js is v${installed}`);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
return {ok: problems.length === 0, installed, found, problems};
|
|
224
|
+
}
|
|
225
|
+
|
|
172
226
|
async function build() {
|
|
173
227
|
// Clean and recreate _publish/
|
|
174
228
|
if (existsSync(OUT)) rmSync(OUT, {recursive: true, force: true});
|
|
@@ -184,13 +238,39 @@ async function build() {
|
|
|
184
238
|
}
|
|
185
239
|
}
|
|
186
240
|
|
|
187
|
-
// Strip prepublishOnly from _publish/package.json
|
|
241
|
+
// Strip prepublishOnly from _publish/package.json - the guard is for the
|
|
188
242
|
// source tree only; running npm publish inside _publish/ must succeed.
|
|
189
243
|
const pkgPath = join(OUT, 'package.json');
|
|
190
244
|
const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'));
|
|
191
245
|
delete pkg.scripts?.prepublishOnly;
|
|
192
246
|
writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n');
|
|
193
247
|
|
|
248
|
+
// Scrub this machine's local config out of the staged copy. config/site.json
|
|
249
|
+
// is a working file on a development install, so it carries whatever SMTP
|
|
250
|
+
// host and site URL were last set here - published releases up to 0.43.1 put
|
|
251
|
+
// a LAN address on the public registry and handed every manual installer a
|
|
252
|
+
// mail host that does not exist for them. Same reasoning as the analytics
|
|
253
|
+
// counters in .npmignore: runtime state must not ride along in the tarball.
|
|
254
|
+
const sitePath = join(OUT, 'config', 'site.json');
|
|
255
|
+
if (existsSync(sitePath)) {
|
|
256
|
+
const site = JSON.parse(readFileSync(sitePath, 'utf8'));
|
|
257
|
+
site.url = 'http://localhost:4096';
|
|
258
|
+
if (site.smtp) {
|
|
259
|
+
site.smtp = {
|
|
260
|
+
host: '', port: 587, user: '', pass: '',
|
|
261
|
+
secure: false, fromAddress: '', fromName: '',
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
writeFileSync(sitePath, JSON.stringify(site, null, 4) + '\n');
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// .bak files are migration leftovers from whichever machine built the
|
|
268
|
+
// release - never something an installer should receive.
|
|
269
|
+
for (const rel of ['config/navigation.json.bak', 'config/connections.json.bak']) {
|
|
270
|
+
const bak = join(OUT, rel);
|
|
271
|
+
if (existsSync(bak)) rmSync(bak, {force: true});
|
|
272
|
+
}
|
|
273
|
+
|
|
194
274
|
// Copy plugin server-side code verbatim
|
|
195
275
|
const pluginAsIs = await collectFiles('{' + PLUGIN_AS_IS_PATTERNS.join(',') + '}');
|
|
196
276
|
for (const rel of pluginAsIs) {
|
|
@@ -212,6 +292,20 @@ async function build() {
|
|
|
212
292
|
'admin/dist/domma/domma-tools.css',
|
|
213
293
|
'admin/dist/domma/domma-tools.min.js',
|
|
214
294
|
];
|
|
295
|
+
|
|
296
|
+
// Version-skew gate - see verifyDommaToolsVersion() for why this cannot be left to npm.
|
|
297
|
+
const toolsCheck = verifyDommaToolsVersion(dommaTools);
|
|
298
|
+
if (toolsCheck.ok) {
|
|
299
|
+
console.log(`Domma Tools v${toolsCheck.installed} - matches installed domma-js`);
|
|
300
|
+
} else {
|
|
301
|
+
for (const problem of toolsCheck.problems) console.error(` ${problem}`);
|
|
302
|
+
throw new Error(
|
|
303
|
+
`Domma Tools is out of step with domma-js v${toolsCheck.installed} - copy `
|
|
304
|
+
+ `domma-tools.min.js and domma-tools.css from that version's build into admin/dist/domma/, `
|
|
305
|
+
+ `then rebuild`,
|
|
306
|
+
);
|
|
307
|
+
}
|
|
308
|
+
|
|
215
309
|
for (const rel of dommaTools) {
|
|
216
310
|
const src = join(ROOT, rel);
|
|
217
311
|
if (existsSync(src)) {
|
|
@@ -244,16 +338,16 @@ async function build() {
|
|
|
244
338
|
|
|
245
339
|
await stampPluginVersions();
|
|
246
340
|
|
|
247
|
-
console.log(`Built into _publish/
|
|
341
|
+
console.log(`Built into _publish/ - JS: ${jsCount} minified, CSS: ${cssCount} minified, ${totalCopied} entries copied`);
|
|
248
342
|
|
|
249
|
-
// Integrity gate
|
|
343
|
+
// Integrity gate - fail the build (and therefore `make pack` / `make
|
|
250
344
|
// release-npm`) if the staged set ships any ESM import pointing at a file
|
|
251
345
|
// that isn't there. This is what stops a "shipped code that imports a missing
|
|
252
346
|
// file" release (the 0.22.1 menu-decor.mjs incident) from ever publishing.
|
|
253
347
|
const integrity = await verifyAssetImports({projectRoot: OUT});
|
|
254
348
|
console.log(formatReport(integrity, '_publish/'));
|
|
255
349
|
if (!integrity.ok) {
|
|
256
|
-
throw new Error(`asset integrity check failed
|
|
350
|
+
throw new Error(`asset integrity check failed - ${integrity.missing.length} dangling import(s); refusing to package a broken release`);
|
|
257
351
|
}
|
|
258
352
|
}
|
|
259
353
|
|
package/scripts/create-plugin.js
CHANGED
|
@@ -69,12 +69,12 @@ const templateDir = join(ROOT, 'plugins', '_template');
|
|
|
69
69
|
const targetDir = join(ROOT, 'plugins', slug);
|
|
70
70
|
|
|
71
71
|
if (!existsSync(templateDir)) {
|
|
72
|
-
console.error(`Template not found at plugins/_template/
|
|
72
|
+
console.error(`Template not found at plugins/_template/ - cannot scaffold.`);
|
|
73
73
|
process.exit(1);
|
|
74
74
|
}
|
|
75
75
|
|
|
76
76
|
if (existsSync(targetDir)) {
|
|
77
|
-
console.error(`plugins/${slug}/ already exists
|
|
77
|
+
console.error(`plugins/${slug}/ already exists - choose a different name.`);
|
|
78
78
|
process.exit(1);
|
|
79
79
|
}
|
|
80
80
|
|
package/scripts/fresh.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
* Domma CMS
|
|
3
|
+
* Domma CMS - Fresh Install
|
|
4
4
|
* Chains: reset → setup
|
|
5
5
|
* Gives you a clean CMS install ready to use.
|
|
6
6
|
* Run: npm run fresh
|
|
@@ -24,7 +24,7 @@ function run(script) {
|
|
|
24
24
|
|
|
25
25
|
console.log('');
|
|
26
26
|
console.log(' ╔══════════════════════════════════════╗');
|
|
27
|
-
console.log(' ║ Domma CMS
|
|
27
|
+
console.log(' ║ Domma CMS - Fresh Install ║');
|
|
28
28
|
console.log(' ║ reset → setup ║');
|
|
29
29
|
console.log(' ╚══════════════════════════════════════╝');
|
|
30
30
|
console.log('');
|
|
@@ -36,7 +36,7 @@ async function writeEnv(env) {
|
|
|
36
36
|
|
|
37
37
|
const env = await readEnv();
|
|
38
38
|
if (env.INSTANCE_SECRET && env.INSTANCE_SECRET.length >= 32) {
|
|
39
|
-
console.log(' ✓ INSTANCE_SECRET already set
|
|
39
|
+
console.log(' ✓ INSTANCE_SECRET already set - skipping.');
|
|
40
40
|
process.exit(0);
|
|
41
41
|
}
|
|
42
42
|
|
package/scripts/pro.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
* Domma CMS
|
|
3
|
+
* Domma CMS - Pro Mode Toggle
|
|
4
4
|
*
|
|
5
5
|
* Enables or disables MongoDB storage adapter (Pro feature).
|
|
6
6
|
*
|
|
7
7
|
* Usage:
|
|
8
|
-
* npm run pro enable
|
|
9
|
-
* npm run pro disable
|
|
10
|
-
* npm run pro status
|
|
8
|
+
* npm run pro enable - copy connections.json.example, prompt for URI/DB, install mongodb
|
|
9
|
+
* npm run pro disable - back up connections.json, optionally uninstall mongodb
|
|
10
|
+
* npm run pro status - report current mode and connection summary
|
|
11
11
|
*/
|
|
12
12
|
import {createInterface} from 'node:readline/promises';
|
|
13
13
|
import {copyFile, readFile, rename, writeFile} from 'node:fs/promises';
|
|
@@ -75,7 +75,7 @@ async function cmdStatus() {
|
|
|
75
75
|
}
|
|
76
76
|
|
|
77
77
|
console.log(' Mode : Pro (MongoDB)');
|
|
78
|
-
console.log(` mongodb : ${mongoInstalled ? 'installed ✓' : 'NOT installed
|
|
78
|
+
console.log(` mongodb : ${mongoInstalled ? 'installed ✓' : 'NOT installed - run: npm install mongodb'}`);
|
|
79
79
|
|
|
80
80
|
try {
|
|
81
81
|
const raw = await readFile(CONNECTIONS_FILE, 'utf8');
|
|
@@ -95,7 +95,7 @@ async function cmdStatus() {
|
|
|
95
95
|
}
|
|
96
96
|
}
|
|
97
97
|
} catch (err) {
|
|
98
|
-
console.log(` Warning : Could not parse connections.json
|
|
98
|
+
console.log(` Warning : Could not parse connections.json - ${err.message}`);
|
|
99
99
|
}
|
|
100
100
|
|
|
101
101
|
console.log('');
|
|
@@ -105,7 +105,7 @@ async function cmdEnable() {
|
|
|
105
105
|
section('Enable Pro Mode');
|
|
106
106
|
|
|
107
107
|
if (existsSync(CONNECTIONS_FILE)) {
|
|
108
|
-
console.log(' ✓ config/connections.json already exists
|
|
108
|
+
console.log(' ✓ config/connections.json already exists - Pro mode is already enabled.');
|
|
109
109
|
console.log(' Run "npm run pro status" to review the current configuration.\n');
|
|
110
110
|
return;
|
|
111
111
|
}
|
|
@@ -140,7 +140,7 @@ async function cmdEnable() {
|
|
|
140
140
|
await writeFile(CONNECTIONS_FILE, JSON.stringify(cfg, null, 4) + '\n', 'utf8');
|
|
141
141
|
|
|
142
142
|
console.log('\n ✓ config/connections.json updated with your connection details.');
|
|
143
|
-
console.log(' (This file is gitignored
|
|
143
|
+
console.log(' (This file is gitignored - credentials stay local.)\n');
|
|
144
144
|
|
|
145
145
|
// Install mongodb package
|
|
146
146
|
console.log(' Installing mongodb package…');
|
|
@@ -168,13 +168,13 @@ async function cmdDisable() {
|
|
|
168
168
|
section('Disable Pro Mode');
|
|
169
169
|
|
|
170
170
|
if (!existsSync(CONNECTIONS_FILE)) {
|
|
171
|
-
console.log(' Already in file-based mode
|
|
171
|
+
console.log(' Already in file-based mode - nothing to disable.\n');
|
|
172
172
|
return;
|
|
173
173
|
}
|
|
174
174
|
|
|
175
175
|
// Back up connections.json
|
|
176
176
|
if (existsSync(CONNECTIONS_BAK)) {
|
|
177
|
-
console.log(' Note: connections.json.bak already exists
|
|
177
|
+
console.log(' Note: connections.json.bak already exists - it will be overwritten.');
|
|
178
178
|
}
|
|
179
179
|
|
|
180
180
|
await rename(CONNECTIONS_FILE, CONNECTIONS_BAK);
|
|
@@ -195,7 +195,7 @@ async function cmdDisable() {
|
|
|
195
195
|
console.log('\n Warning: npm uninstall mongodb failed. Remove it manually if needed.');
|
|
196
196
|
}
|
|
197
197
|
} else {
|
|
198
|
-
console.log(' Skipping uninstall
|
|
198
|
+
console.log(' Skipping uninstall - mongodb package remains (unused).');
|
|
199
199
|
}
|
|
200
200
|
|
|
201
201
|
console.log('');
|
|
@@ -209,7 +209,7 @@ async function cmdDisable() {
|
|
|
209
209
|
function printUsage() {
|
|
210
210
|
console.log('');
|
|
211
211
|
console.log(' ┌─────────────────────────────────────┐');
|
|
212
|
-
console.log(' │ Domma CMS
|
|
212
|
+
console.log(' │ Domma CMS - Pro Mode Toggle │');
|
|
213
213
|
console.log(' └─────────────────────────────────────┘');
|
|
214
214
|
console.log('');
|
|
215
215
|
console.log(' Usage: npm run pro <command>');
|
package/scripts/reset.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
* Domma CMS
|
|
3
|
+
* Domma CMS - Factory Reset
|
|
4
4
|
* Wipes all content, users, and resets config to defaults.
|
|
5
5
|
* Run: npm run reset
|
|
6
6
|
*/
|
|
@@ -74,7 +74,7 @@ async function resetEnv() {
|
|
|
74
74
|
|
|
75
75
|
console.log('');
|
|
76
76
|
console.log(' ┌──────────────────────────────────┐');
|
|
77
|
-
console.log(' │ Domma CMS
|
|
77
|
+
console.log(' │ Domma CMS - Factory Reset │');
|
|
78
78
|
console.log(' └──────────────────────────────────┘');
|
|
79
79
|
console.log('');
|
|
80
80
|
console.log(' This will permanently delete:');
|
package/scripts/setup.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
* Domma CMS
|
|
3
|
+
* Domma CMS - Interactive Setup Wizard
|
|
4
4
|
*
|
|
5
5
|
* Run once after cloning: npm run setup
|
|
6
|
-
* Operates directly on files
|
|
6
|
+
* Operates directly on files - does NOT import the Fastify server.
|
|
7
7
|
*
|
|
8
8
|
* Steps:
|
|
9
9
|
* 1. Generate JWT_SECRET (skips if already set)
|
|
@@ -134,7 +134,7 @@ console.log(' │ Domma CMS Setup Wizard │');
|
|
|
134
134
|
console.log(' └────────────────────────────┘');
|
|
135
135
|
|
|
136
136
|
// ---------------------------------------------------------------------------
|
|
137
|
-
// Step 1
|
|
137
|
+
// Step 1 - JWT Secret
|
|
138
138
|
// ---------------------------------------------------------------------------
|
|
139
139
|
section('1. JWT Secret');
|
|
140
140
|
|
|
@@ -145,7 +145,7 @@ const isPlaceholder = !existingSecret
|
|
|
145
145
|
|| existingSecret.length < 32;
|
|
146
146
|
|
|
147
147
|
if (!isPlaceholder) {
|
|
148
|
-
console.log(' ✓ JWT_SECRET already configured
|
|
148
|
+
console.log(' ✓ JWT_SECRET already configured - skipping.');
|
|
149
149
|
} else {
|
|
150
150
|
const secret = randomBytes(48).toString('hex');
|
|
151
151
|
env.JWT_SECRET = secret;
|
|
@@ -155,18 +155,18 @@ if (!isPlaceholder) {
|
|
|
155
155
|
}
|
|
156
156
|
|
|
157
157
|
// ---------------------------------------------------------------------------
|
|
158
|
-
// Step 2
|
|
158
|
+
// Step 2 - Instance Secret
|
|
159
159
|
// ---------------------------------------------------------------------------
|
|
160
160
|
section('2. Instance Secret');
|
|
161
161
|
|
|
162
|
-
// Distinct name from step 1's existingSecret
|
|
162
|
+
// Distinct name from step 1's existingSecret - both are module-scope consts,
|
|
163
163
|
// so reusing the identifier made the whole file a SyntaxError and setup could
|
|
164
164
|
// not run at all.
|
|
165
165
|
const existingInstanceSecret = env.INSTANCE_SECRET ?? '';
|
|
166
166
|
const isInstanceSecretSet = existingInstanceSecret && existingInstanceSecret.length >= 32;
|
|
167
167
|
|
|
168
168
|
if (isInstanceSecretSet) {
|
|
169
|
-
console.log(' ✓ INSTANCE_SECRET already configured
|
|
169
|
+
console.log(' ✓ INSTANCE_SECRET already configured - skipping.');
|
|
170
170
|
} else {
|
|
171
171
|
const secret = randomBytes(32).toString('hex');
|
|
172
172
|
env.INSTANCE_SECRET = secret;
|
|
@@ -175,13 +175,13 @@ if (isInstanceSecretSet) {
|
|
|
175
175
|
}
|
|
176
176
|
|
|
177
177
|
// ---------------------------------------------------------------------------
|
|
178
|
-
// Step 3
|
|
178
|
+
// Step 3 - Admin Account
|
|
179
179
|
// ---------------------------------------------------------------------------
|
|
180
180
|
section('3. Admin Account');
|
|
181
181
|
|
|
182
182
|
const userCount = await countUsers();
|
|
183
183
|
if (userCount > 0) {
|
|
184
|
-
console.log(` ✓ ${userCount} user(s) already exist
|
|
184
|
+
console.log(` ✓ ${userCount} user(s) already exist - skipping admin creation.`);
|
|
185
185
|
} else {
|
|
186
186
|
console.log(' No users found. Create the first admin account.\n');
|
|
187
187
|
|
|
@@ -228,7 +228,7 @@ if (userCount > 0) {
|
|
|
228
228
|
}
|
|
229
229
|
|
|
230
230
|
// ---------------------------------------------------------------------------
|
|
231
|
-
// Step 4
|
|
231
|
+
// Step 4 - Site Identity
|
|
232
232
|
// ---------------------------------------------------------------------------
|
|
233
233
|
section('4. Site Identity');
|
|
234
234
|
|
|
@@ -256,7 +256,7 @@ section('4. Site Identity');
|
|
|
256
256
|
}
|
|
257
257
|
|
|
258
258
|
// ---------------------------------------------------------------------------
|
|
259
|
-
// Step 5
|
|
259
|
+
// Step 5 - Theme
|
|
260
260
|
// ---------------------------------------------------------------------------
|
|
261
261
|
section('5. Theme');
|
|
262
262
|
|
|
@@ -268,7 +268,7 @@ THEMES.forEach((t, i) => {
|
|
|
268
268
|
|
|
269
269
|
{
|
|
270
270
|
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
271
|
-
const choice = (await rl.question(`\n Choice (1
|
|
271
|
+
const choice = (await rl.question(`\n Choice (1-${THEMES.length}, Enter = charcoal-dark): `)).trim();
|
|
272
272
|
rl.close();
|
|
273
273
|
|
|
274
274
|
const idx = parseInt(choice, 10) - 1;
|
|
@@ -282,7 +282,7 @@ THEMES.forEach((t, i) => {
|
|
|
282
282
|
}
|
|
283
283
|
|
|
284
284
|
// ---------------------------------------------------------------------------
|
|
285
|
-
// Step 6
|
|
285
|
+
// Step 6 - Port
|
|
286
286
|
// ---------------------------------------------------------------------------
|
|
287
287
|
section('6. Server Port');
|
|
288
288
|
|
|
@@ -305,7 +305,7 @@ section('6. Server Port');
|
|
|
305
305
|
// Git hooks
|
|
306
306
|
// ---------------------------------------------------------------------------
|
|
307
307
|
// The hooks in .githooks/ are committed, but git will not run them until this
|
|
308
|
-
// clone points at that directory
|
|
308
|
+
// clone points at that directory - core.hooksPath is local config, so it cannot
|
|
309
309
|
// travel with the repository. Every clone that misses this step silently loses
|
|
310
310
|
// both hooks: the commit-msg trailer strip and the pre-commit minified-source
|
|
311
311
|
// guard. Enable it here so a fresh clone is protected by default.
|
|
@@ -315,7 +315,7 @@ try {
|
|
|
315
315
|
execSync('git config core.hooksPath .githooks', {stdio: 'ignore'});
|
|
316
316
|
console.log(' ✓ Git hooks enabled (core.hooksPath = .githooks)');
|
|
317
317
|
} catch {
|
|
318
|
-
// Not a git checkout
|
|
318
|
+
// Not a git checkout - an npm install of the published package, most
|
|
319
319
|
// likely. Nothing to enable, and nothing worth warning about.
|
|
320
320
|
}
|
|
321
321
|
|
package/scripts/users.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
* Domma CMS
|
|
3
|
+
* Domma CMS - User Management CLI
|
|
4
4
|
*
|
|
5
5
|
* Run from the project root:
|
|
6
6
|
* npm run users -- <command> [args] [options]
|
|
@@ -105,7 +105,7 @@ async function cmdList() {
|
|
|
105
105
|
name: u.name,
|
|
106
106
|
role: u.role,
|
|
107
107
|
active: u.isActive ? 'yes' : 'no',
|
|
108
|
-
last: u.lastLogin ? u.lastLogin.slice(0, 16).replace('T', ' ') : '
|
|
108
|
+
last: u.lastLogin ? u.lastLogin.slice(0, 16).replace('T', ' ') : '-'
|
|
109
109
|
}));
|
|
110
110
|
printTable(['email', 'name', 'role', 'active', 'last'], rows);
|
|
111
111
|
}
|
|
@@ -203,7 +203,7 @@ async function resolvePassword({confirm: requireConfirm = false} = {}) {
|
|
|
203
203
|
if (flags.has('generate')) {
|
|
204
204
|
const pw = generatePassword(20);
|
|
205
205
|
console.log(`\n Generated password: ${pw}`);
|
|
206
|
-
console.log(' (this is the only time it will be shown
|
|
206
|
+
console.log(' (this is the only time it will be shown - copy it now)\n');
|
|
207
207
|
return pw;
|
|
208
208
|
}
|
|
209
209
|
|
|
@@ -221,7 +221,7 @@ function generatePassword(bytes) {
|
|
|
221
221
|
}
|
|
222
222
|
|
|
223
223
|
/**
|
|
224
|
-
* Prompt with masked echo
|
|
224
|
+
* Prompt with masked echo - reads raw TTY input so keystrokes don't appear on screen.
|
|
225
225
|
* Falls back to a plain (visible) readline when stdin isn't a TTY (e.g. piped input).
|
|
226
226
|
*
|
|
227
227
|
* @param {string} prompt
|
|
@@ -286,7 +286,7 @@ function printTable(headers, rows) {
|
|
|
286
286
|
|
|
287
287
|
function printHelp() {
|
|
288
288
|
console.log(`
|
|
289
|
-
Domma CMS
|
|
289
|
+
Domma CMS - User Management CLI
|
|
290
290
|
|
|
291
291
|
Usage:
|
|
292
292
|
npm run users -- <command> [args] [options]
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Asset Import Verifier
|
|
2
|
+
* Asset Import Verifier - build/publish gate
|
|
3
3
|
*
|
|
4
4
|
* Statically resolves every local ESM import under the browser-facing code
|
|
5
5
|
* (admin/ + public/) against the files that actually exist, and fails if any
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* import natively (no bundler). If a release ships consumer code that imports a
|
|
12
12
|
* new module but forgets to include the module file (as 0.22.1 did with
|
|
13
13
|
* /public/js/menu-decor.mjs), every page 404s on the import and the whole site
|
|
14
|
-
* breaks
|
|
14
|
+
* breaks - with no signal but a browser console error, and it cascades to every
|
|
15
15
|
* downstream project that copies these dirs on update. A bundler would catch
|
|
16
16
|
* this at build time; because we don't bundle, this gate stands in for that.
|
|
17
17
|
*
|
|
@@ -21,8 +21,8 @@
|
|
|
21
21
|
* Exits 1 (with a report) on any dangling import, 0 otherwise. Wired into
|
|
22
22
|
* scripts/build.js so `make pack` / `make release-npm` cannot ship a broken set.
|
|
23
23
|
*
|
|
24
|
-
* Scope (deliberately conservative
|
|
25
|
-
* only STATIC, LOCAL specifiers with an explicit extension are checked
|
|
24
|
+
* Scope (deliberately conservative - zero false positives is the whole point):
|
|
25
|
+
* only STATIC, LOCAL specifiers with an explicit extension are checked - the
|
|
26
26
|
* only kind this codebase emits. Bare (npm) specifiers, URLs, dynamic/template
|
|
27
27
|
* specifiers, commented-out imports, and extension-less paths are all ignored.
|
|
28
28
|
*/
|
|
@@ -160,12 +160,12 @@ export async function verifyAssetImports({ projectRoot, roots = ['admin', 'publi
|
|
|
160
160
|
|
|
161
161
|
export function formatReport(result, label) {
|
|
162
162
|
if (result.ok) {
|
|
163
|
-
return `[verify-assets] OK
|
|
163
|
+
return `[verify-assets] OK - ${result.scanned} files scanned, all local imports resolve (${label}).`;
|
|
164
164
|
}
|
|
165
165
|
const lines = [
|
|
166
166
|
'',
|
|
167
167
|
' ╔════════════════════════════════════════════════════════════════╗',
|
|
168
|
-
' ║ ASSET INTEGRITY FAILURE
|
|
168
|
+
' ║ ASSET INTEGRITY FAILURE - dangling front-end imports detected ║',
|
|
169
169
|
' ╚════════════════════════════════════════════════════════════════╝',
|
|
170
170
|
` Context: ${label}`,
|
|
171
171
|
` ${result.missing.length} import(s) reference files that do not exist:`,
|
package/server/config.js
CHANGED
|
@@ -43,7 +43,7 @@ export function saveConfig(name, data) {
|
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
/**
|
|
46
|
-
* Structural config
|
|
46
|
+
* Structural config - loaded once at startup.
|
|
47
47
|
* Do not use for admin-editable settings (site, navigation, presets, plugins).
|
|
48
48
|
*/
|
|
49
49
|
const serverConfig = loadJson('server');
|