domma-cms 0.54.2 → 0.66.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 +151 -77
- package/README.md +4 -4
- 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/js/api.js +1 -1
- package/admin/js/app.js +3 -3
- package/admin/js/lib/page-picker.js +1 -0
- package/admin/js/lib/plugin-accent.js +1 -0
- package/admin/js/lib/plugin-chrome.js +1 -0
- package/admin/js/lib/shortcode-context-menu.js +2 -2
- package/admin/js/lib/sidebar-grouping.js +1 -1
- package/admin/js/lib/sidebar-grouping.test.js +1 -1
- package/admin/js/lib/sidebar-renderer.js +4 -4
- package/admin/js/lib/slideover-resizable.js +1 -0
- package/admin/js/templates/context-menu-editor.html +212 -0
- package/admin/js/templates/context-menus.html +16 -0
- package/admin/js/templates/menu-editor.html +19 -25
- package/admin/js/templates/plugin-code.html +1 -1
- package/{plugins/site-search/admin/templates/site-search.html → admin/js/templates/search.html} +50 -4
- package/admin/js/templates/settings.html +27 -97
- package/admin/js/templates/theme.html +173 -0
- package/admin/js/views/context-menu-editor.js +55 -0
- package/admin/js/views/context-menus.js +5 -0
- package/admin/js/views/form-editor.js +7 -7
- package/admin/js/views/index.js +1 -1
- package/admin/js/views/menu-editor.js +13 -13
- package/admin/js/views/plugin-marketplace.js +1 -1
- package/admin/js/views/plugins.js +25 -23
- package/admin/js/views/search.js +1 -0
- package/admin/js/views/settings.js +3 -3
- package/admin/js/views/theme.js +1 -0
- package/bin/cli.js +11 -2
- package/bin/lib/smtp-defaults.js +53 -0
- package/bin/update.js +13 -2
- package/config/menus/admin-sidebar.json +129 -23
- package/config/plugins.json +5 -5
- package/config/search.json +13 -0
- package/config/theme.json +18 -0
- package/package.json +12 -4
- package/plugins/_lib/admin/mail/compose-window.js +914 -0
- package/plugins/_lib/admin/mail/contacts.js +301 -0
- package/plugins/_lib/admin/mail/diagnostics-section.js +133 -0
- package/plugins/_lib/admin/mail/folder-tree.js +254 -0
- package/plugins/_lib/admin/mail/identity.js +480 -0
- package/plugins/_lib/admin/mail/image-senders-section.js +131 -0
- package/plugins/_lib/admin/mail/keyboard.js +136 -0
- package/plugins/_lib/admin/mail/mail.css +1 -0
- package/plugins/_lib/admin/mail/mail.html +71 -0
- package/plugins/_lib/admin/mail/panes.js +253 -0
- package/plugins/_lib/admin/mail/reader-view.js +4453 -0
- package/plugins/_lib/admin/mail/resizable.js +26 -0
- package/plugins/_lib/admin/mail/rules.js +343 -0
- package/plugins/_lib/admin/mail/scheduling.js +203 -0
- package/plugins/_lib/admin/mail/section-kit.js +277 -0
- package/plugins/_lib/admin/mail/templates.js +238 -0
- package/plugins/_lib/admin/mail/threads.js +200 -0
- package/plugins/_lib/admin/mail/vacation.js +269 -0
- package/plugins/_lib/admin/ui/help.css +1 -0
- package/plugins/_lib/admin/ui/help.js +174 -0
- package/plugins/_lib/admin/ui/resizable.js +151 -0
- package/plugins/_lib/dataStore.js +117 -0
- package/plugins/_lib/mail/accounts.js +919 -0
- package/plugins/_lib/mail/bodyTokens.js +101 -0
- package/plugins/_lib/mail/compose.js +256 -0
- package/plugins/_lib/mail/defaults.js +57 -0
- package/plugins/_lib/mail/diagnostics.js +154 -0
- package/plugins/_lib/mail/envelope.js +161 -0
- package/plugins/_lib/mail/folders.js +192 -0
- package/plugins/_lib/mail/handoff.js +274 -0
- package/plugins/_lib/mail/imapPool.js +293 -0
- package/plugins/_lib/mail/mbox.js +74 -0
- package/plugins/_lib/mail/pollSchedule.js +79 -0
- package/plugins/_lib/mail/poller.js +135 -0
- package/plugins/_lib/mail/priority.js +138 -0
- package/plugins/_lib/mail/readRoutes.js +680 -0
- package/plugins/_lib/mail/render.js +291 -0
- package/plugins/_lib/mail/ruleRunner.js +152 -0
- package/plugins/_lib/mail/scheduler.js +254 -0
- package/plugins/_lib/mail/secretbox.js +229 -0
- package/plugins/_lib/mail/send.js +396 -0
- package/plugins/_lib/mail/store.js +1002 -0
- package/plugins/_lib/mail/sync.js +292 -0
- package/plugins/_lib/mail/syncPlan.js +126 -0
- package/plugins/_lib/mail/syncSelection.js +82 -0
- package/plugins/_lib/mail/unsubscribe.js +183 -0
- package/plugins/_lib/mail/vacationRunner.js +114 -0
- package/plugins/_lib/mail/write.js +473 -0
- package/plugins/_lib/schemaSync.js +83 -0
- package/plugins/_template/admin/css/index.css +0 -0
- package/plugins/_template/admin/templates/index.html +4 -4
- package/plugins/_template/admin/views/index.js +7 -0
- package/plugins/analytics/admin/css/index.css +1 -0
- package/plugins/analytics/admin/templates/analytics.html +22 -13
- package/plugins/analytics/plugin.json +3 -0
- package/plugins/blog/admin/css/index.css +1 -0
- package/plugins/blog/admin/templates/blog.html +30 -18
- package/plugins/blog/admin/templates/categories.html +2 -2
- package/plugins/blog/admin/templates/comments.html +2 -2
- package/plugins/blog/admin/templates/post-editor.html +34 -34
- package/plugins/blog/admin/templates/settings.html +6 -3
- package/plugins/blog/admin/views/blog.js +8 -5
- package/plugins/blog/admin/views/categories.js +5 -10
- package/plugins/blog/admin/views/comments.js +5 -5
- package/plugins/blog/admin/views/post-editor.js +39 -20
- package/plugins/blog/admin/views/settings.js +52 -50
- package/plugins/blog/collections/categories/schema.json +7 -6
- package/plugins/blog/collections/comments/schema.json +11 -10
- package/plugins/blog/collections/posts/schema.json +14 -13
- package/plugins/blog/plugin.js +36 -13
- package/plugins/blog/plugin.json +13 -5
- package/plugins/blog/plugin.public.js +312 -0
- package/plugins/contacts/admin/css/index.css +1 -0
- package/plugins/contacts/admin/templates/contacts.html +128 -0
- package/plugins/contacts/admin/views/contacts.js +237 -4
- package/plugins/contacts/collections/user-contacts/schema.json +108 -0
- package/plugins/contacts/plugin.js +214 -27
- package/plugins/contacts/plugin.json +4 -1
- package/plugins/invoice/admin/css/index.css +1 -0
- package/plugins/invoice/admin/templates/editor.html +140 -49
- package/plugins/invoice/admin/templates/index.html +153 -23
- package/plugins/invoice/admin/templates/issuers.html +2 -5
- package/plugins/invoice/admin/templates/receivers.html +2 -5
- package/plugins/invoice/admin/views/contacts-source.js +266 -0
- package/plugins/invoice/admin/views/editor.js +366 -199
- package/plugins/invoice/admin/views/export.js +199 -0
- package/plugins/invoice/admin/views/help-content.js +61 -0
- package/plugins/invoice/admin/views/index.js +582 -94
- package/plugins/invoice/admin/views/issuers.js +24 -17
- package/plugins/invoice/admin/views/media.js +172 -0
- package/plugins/invoice/admin/views/party-view.js +305 -67
- package/plugins/invoice/admin/views/payments.js +127 -0
- package/plugins/invoice/admin/views/print.js +130 -0
- package/plugins/invoice/admin/views/receivers.js +49 -16
- package/plugins/invoice/admin/views/send.js +212 -0
- package/plugins/invoice/admin/views/settings.js +594 -0
- package/plugins/invoice/admin/views/view-lifecycle.js +33 -0
- package/plugins/invoice/collections/invoice-issuers/schema.json +77 -11
- package/plugins/invoice/collections/invoice-receivers/schema.json +10 -9
- package/plugins/invoice/collections/invoices/schema.json +19 -13
- package/plugins/invoice/config.js +27 -6
- package/plugins/invoice/pdf.js +164 -0
- package/plugins/invoice/plugin.js +1217 -44
- package/plugins/invoice/plugin.json +10 -9
- package/plugins/invoice/templates/_base.css +1 -0
- package/plugins/invoice/templates/classic-nologo.html +100 -0
- package/plugins/invoice/templates/classic.html +91 -0
- package/plugins/invoice/templates/invoice-print.html +24 -0
- package/plugins/invoice/templates/minimal.html +99 -0
- package/plugins/invoice/templates/modern-nologo.html +114 -0
- package/plugins/invoice/templates/modern.html +113 -0
- package/plugins/invoice/templates/templates.json +11 -0
- package/plugins/mail-reader/admin/views/mail.js +19 -0
- package/plugins/mail-reader/config.js +7 -0
- package/plugins/mail-reader/plugin.js +48 -0
- package/plugins/mail-reader/plugin.json +33 -0
- package/plugins/notes/admin/views/notes.js +1 -1
- package/plugins/notes/plugin.json +2 -2
- package/plugins/surveys/lib/audience.js +37 -0
- package/plugins/surveys/lib/campaigns.js +43 -0
- package/plugins/surveys/lib/ledger.js +110 -0
- package/plugins/surveys/lib/sending.js +106 -0
- package/plugins/surveys/lib/stats.js +62 -0
- package/plugins/surveys/lib/submit.js +95 -0
- package/plugins/surveys/lib/tokens.js +28 -0
- package/plugins/surveys/plugin.public.js +149 -0
- package/plugins/theme-switcher/admin/templates/theme-switcher.html +1 -1
- package/public/css/forms.css +1 -1
- package/public/css/menu-highlight.css +1 -1
- package/public/css/search.css +1 -0
- package/public/css/site.css +1 -1
- package/public/js/collection-context.js +2 -2
- package/public/js/context-menus.js +1 -0
- package/public/js/form-logic-engine.js +1 -1
- package/public/js/forms.js +2 -2
- package/public/js/menu-decor.mjs +1 -1
- package/public/js/search.js +1 -0
- package/public/js/site.js +1 -1
- package/scripts/build.js +37 -3
- package/scripts/copy-domma.js +48 -0
- package/scripts/seed.js +1996 -0
- package/scripts/setup.js +8 -0
- package/server/routes/api/collections.js +34 -0
- package/server/routes/api/context-menus.js +104 -0
- package/server/routes/api/forms.js +42 -3
- package/server/routes/api/notifications.js +69 -19
- package/server/routes/api/plugins.js +50 -6
- package/server/routes/api/search.js +43 -0
- package/server/routes/api/theme.js +69 -0
- package/server/routes/public.js +42 -7
- package/server/server.js +74 -0
- package/server/services/adapters/FileAdapter.js +6 -1
- package/server/services/content.js +26 -0
- package/server/services/contextMenus.js +477 -0
- package/server/services/email.js +29 -3
- package/server/services/health.js +23 -2
- package/server/services/markdown.js +70 -9
- package/server/services/menuRender.js +28 -4
- package/server/services/menus.js +25 -1
- package/server/services/permissionRegistry.js +24 -0
- package/server/services/pluginFiles.js +52 -11
- package/server/services/plugins.js +229 -6
- package/server/services/renderer.js +148 -22
- package/server/services/roles.js +1 -1
- package/server/services/search-migration.js +82 -0
- package/server/services/search.js +413 -0
- package/server/services/sidebar-migration.js +1 -0
- package/server/services/themeSettings.js +541 -0
- package/server/services/users.js +8 -0
- package/server/templates/page.html +4 -2
- package/plugins/contacts/data/contacts.json +0 -20
- package/plugins/notes/data/notes.json +0 -1
- package/plugins/site-search/admin/views/site-search.js +0 -116
- package/plugins/site-search/config.js +0 -15
- package/plugins/site-search/plugin.js +0 -188
- package/plugins/site-search/plugin.json +0 -40
- package/plugins/site-search/public/inject-body.html +0 -17
- package/plugins/site-search/public/inject-head.html +0 -1
- package/plugins/site-search/public/search.css +0 -1
- package/plugins/site-search/public/search.js +0 -1
- package/plugins/todo/data/todos.json +0 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import{api as
|
|
2
|
-
<div class="menu-tree-row menu-tree-row--separator" data-id="${o.id}" style="margin-left:${
|
|
1
|
+
import{api as k}from"../api.js";import{colourToCss as xt,HIGHLIGHT_HOVER as rt,HIGHLIGHT_ACTIVE as pt,HIGHLIGHT_HOVER_LABELS as mt,HIGHLIGHT_ACTIVE_LABELS as ft}from"/public/js/menu-decor.mjs?v=20260918-item-highlight";import{openIconPicker as At}from"../lib/card-builder.js";import{applyDrop as _t}from"../lib/menu-tree.js";let kt=1;const G=()=>"i_"+kt++,It=760;function ut(t,d=null,s=[]){for(const i of t||[]){const n=G();if(i&&i.type==="separator"){s.push({id:n,parentId:d,type:"separator"});continue}if(i&&i.type==="spacer"){s.push({id:n,parentId:d,type:"spacer",size:i.size||"md"});continue}s.push({id:n,parentId:d,text:i.text||"",url:i.url||"",icon:i.icon||"",visibility:i.visibility||"",permission:i.permission||"",hidden:!!i.hidden,bundled:!!i.bundled,badge:i.badge?{...i.badge}:null,pill:i.pill?{...i.pill}:null,colour:i.colour||"",highlight:i.highlight?{...i.highlight}:null}),Array.isArray(i.items)&&i.items.length&&ut(i.items,n,s)}return s}function Ht(t){const d=new Map;for(const i of t)d.has(i.parentId)||d.set(i.parentId,[]),d.get(i.parentId).push(i);function s(i){return(d.get(i)||[]).map(n=>{if(n.type==="separator")return{type:"separator"};if(n.type==="spacer")return{type:"spacer",...n.size&&n.size!=="md"&&{size:n.size}};const o=s(n.id);return{text:(n.text||"").trim(),url:(n.url||"").trim(),...n.icon&&{icon:n.icon.trim()},...n.visibility&&{visibility:n.visibility},...n.permission&&{permission:n.permission},...n.hidden&&{hidden:!0},...n.bundled&&{bundled:!0},...n.badge&&(n.badge.text||n.badge.countFrom)?{badge:{...n.badge.text&&{text:n.badge.text},...n.badge.countFrom&&{countFrom:n.badge.countFrom},...n.badge.variant&&{variant:n.badge.variant}}}:{},...n.pill&&n.pill.variant?{pill:{style:n.pill.style||"filled",variant:n.pill.variant}}:{},...n.colour&&{colour:n.colour},...n.highlight&&(n.highlight.hover||n.highlight.active||n.highlight.accent)?{highlight:{...n.highlight.hover&&{hover:n.highlight.hover},...n.highlight.active&&{active:n.highlight.active},...n.highlight.accent&&{accent:n.highlight.accent}}}:{},...o.length&&{items:o}}})}return s(null)}function h(t){return String(t||"").replace(/&/g,"&").replace(/"/g,""").replace(/</g,"<")}function St(t,d){let s=0,i=d.find(n=>n.id===t);for(;i&&i.parentId;)s++,i=d.find(n=>n.id===i.parentId);return s}function ht(t,d){const{top:s,height:i}=t.getBoundingClientRect(),n=i?(d-s)/i:.5;return n<.25?"above":n>.75?"below":"into"}const gt=["me-drop-above","me-drop-below","me-drop-into"];function st(t){t.get(0).querySelectorAll(".menu-tree-row").forEach(d=>d.classList.remove(...gt))}function Ot(t,d,s){let i=null;t.get(0).querySelectorAll(".menu-tree-row").forEach(n=>{const o=n.querySelector(".menu-tree-grip");n.draggable=!1,o&&(o.addEventListener("mousedown",()=>{n.draggable=!0}),o.addEventListener("mouseup",()=>{n.draggable=!1})),n.addEventListener("dragstart",p=>{i=n.dataset.id,n.style.opacity="0.45",p.dataTransfer.effectAllowed="move";try{p.dataTransfer.setData("text/plain",i)}catch{}}),n.addEventListener("dragend",()=>{n.style.opacity="",n.draggable=!1,i=null,st(t)}),n.addEventListener("dragover",p=>{if(!i||i===n.dataset.id)return;p.preventDefault(),p.dataTransfer.dropEffect="move";const w=ht(n,p.clientY);st(t),n.classList.add(`me-drop-${w}`)}),n.addEventListener("dragleave",()=>n.classList.remove(...gt)),n.addEventListener("drop",p=>{p.preventDefault();const w=n.dataset.id,A=ht(n,p.clientY);if(st(t),!i)return;const y=_t(d(),i,w,A);i=null,y&&s()})})}function jt(t,d,s=()=>""){t.html("");function i(n){for(const o of d.filter(p=>p.parentId===n)){const p=St(o.id,d);if(o.type==="separator"){t.append(`
|
|
2
|
+
<div class="menu-tree-row menu-tree-row--separator" data-id="${o.id}" style="margin-left:${p*28}px">
|
|
3
3
|
<span class="menu-tree-grip" data-icon="move"></span>
|
|
4
4
|
<span class="me-sep-line"><span class="me-sep-label">Separator</span></span>
|
|
5
5
|
<button class="btn btn-sm btn-ghost me-ins-item" data-tooltip="Add item below"><span data-icon="plus"></span></button>
|
|
@@ -10,12 +10,12 @@ import{api as _}from"../api.js";import{colourToCss as bt}from"/public/js/menu-de
|
|
|
10
10
|
<button class="btn btn-sm btn-ghost me-down" data-tooltip="Move down"><span data-icon="arrow-down"></span></button>
|
|
11
11
|
<button class="btn btn-sm btn-ghost me-remove" data-tooltip="Remove"><span data-icon="trash"></span></button>
|
|
12
12
|
</div>
|
|
13
|
-
`);continue}if(o.type==="spacer"){const
|
|
14
|
-
<div class="menu-tree-row menu-tree-row--spacer" data-id="${o.id}" style="margin-left:${
|
|
13
|
+
`);continue}if(o.type==="spacer"){const N=["xs","sm","md","lg","xl","flex"].map(L=>`<option value="${L}"${(o.size||"md")===L?" selected":""}>${L==="flex"?"flexible":L}</option>`).join("");t.append(`
|
|
14
|
+
<div class="menu-tree-row menu-tree-row--spacer" data-id="${o.id}" style="margin-left:${p*28}px">
|
|
15
15
|
<span class="menu-tree-grip" data-icon="move"></span>
|
|
16
16
|
<span class="me-spacer-line">
|
|
17
17
|
<span class="me-spacer-label">Spacer</span>
|
|
18
|
-
<select class="form-input me-spacer-size" data-tooltip="Spacer size - flexible fills the leftover room">${
|
|
18
|
+
<select class="form-input me-spacer-size" data-tooltip="Spacer size - flexible fills the leftover room">${N}</select>
|
|
19
19
|
</span>
|
|
20
20
|
<button class="btn btn-sm btn-ghost me-ins-item" data-tooltip="Add item below"><span data-icon="plus"></span></button>
|
|
21
21
|
<button class="btn btn-sm btn-ghost me-ins-sep" data-tooltip="Add separator below"><span data-icon="minus"></span></button>
|
|
@@ -25,10 +25,10 @@ import{api as _}from"../api.js";import{colourToCss as bt}from"/public/js/menu-de
|
|
|
25
25
|
<button class="btn btn-sm btn-ghost me-down" data-tooltip="Move down"><span data-icon="arrow-down"></span></button>
|
|
26
26
|
<button class="btn btn-sm btn-ghost me-remove" data-tooltip="Remove"><span data-icon="trash"></span></button>
|
|
27
27
|
</div>
|
|
28
|
-
`);continue}const
|
|
29
|
-
<div class="menu-tree-row menu-tree-row--slim${o.hidden?" menu-tree-row--hidden":""}" data-id="${o.id}" style="margin-left:${
|
|
28
|
+
`);continue}const w=o.badge&&(o.badge.text||o.badge.countFrom)?`<span class="dm-menu-badge">${h(String(o.badge.text||"#"))}</span>`:"",A=xt(o.colour),y=A?` style="color:${h(A)}"`:"";t.append(`
|
|
29
|
+
<div class="menu-tree-row menu-tree-row--slim${o.hidden?" menu-tree-row--hidden":""}" data-id="${o.id}" style="margin-left:${p*28}px">
|
|
30
30
|
<span class="menu-tree-grip" data-icon="move"></span>
|
|
31
|
-
<span class="me-row-label"${
|
|
31
|
+
<span class="me-row-label"${y}>${h(o.text||"(untitled)")}${w}</span>
|
|
32
32
|
<span class="me-row-url">${h(o.url||"")}</span>
|
|
33
33
|
<button class="btn btn-sm btn-ghost me-ins-item" data-tooltip="Add item below"><span data-icon="plus"></span></button>
|
|
34
34
|
<button class="btn btn-sm btn-ghost me-ins-sep" data-tooltip="Add separator below"><span data-icon="minus"></span></button>
|
|
@@ -40,8 +40,8 @@ import{api as _}from"../api.js";import{colourToCss as bt}from"/public/js/menu-de
|
|
|
40
40
|
<button class="btn btn-sm btn-secondary me-edit" data-tooltip="Edit"><span data-icon="edit"></span></button>
|
|
41
41
|
<button class="btn btn-sm btn-ghost me-remove" data-tooltip="Remove"><span data-icon="trash"></span></button>
|
|
42
42
|
</div>
|
|
43
|
-
`),i(o.id)}}i(null),Domma.icons.scan(t.get(0)),t.get(0).querySelectorAll("[data-tooltip]").forEach(n=>{E.tooltip(n,{content:n.getAttribute("data-tooltip"),position:"top"})})}function j(t,d){t.find(".menu-tree-row").each(function(){const s=$(this).data("id"),i=d.find(v=>v.id===s);if(!i||i.type==="separator")return;if(i.type==="spacer"){const v=$(this).find(".me-spacer-size");v.length&&(i.size=v.val());return}const n=$(this).find(".me-text");n.length&&(i.text=n.val());const o=$(this).find(".me-url");o.length&&(i.url=o.val());const r=$(this).find(".me-icon");r.length&&(i.icon=r.val());const y=$(this).find(".me-vis");y.length&&(i.visibility=y.val());const x=$(this).find(".me-perm");x.length&&(i.permission=x.val())})}function Lt(t,d){const s={};for(const i of d){const n=t.querySelector(`[name="${i}"]`);n&&(s[i]=n.type==="checkbox"?n.checked:n.value)}return s}function b(t,d={},...s){const i=document.createElement(t);Object.assign(i,d);for(const n of s)n!=null&&i.append(n);return i}function O(t,d,s){const i=b("label",{className:"form-field"},b("span",{className:"form-label",textContent:t}),d);return s&&i.append(b("small",{className:"form-hint",textContent:s,style:"display:block;font-size:.78em;opacity:.7;margin-top:.25rem;line-height:1.35"})),i}function St(t,d){const s=[["","- Everyone -"],["public","Public - anyone, incl. logged-out"],...(d||[]).map(n=>[n.name,`${n.label||n.name} and above`]),["private","Private - super-admin only"]];t&&!s.some(([n])=>n===t)&&s.push([t,`${t} (custom)`]);const i=W(s,t||"");return i.name="visibility",i}function W(t,d){const s=b("select",{className:"form-select"});return t.forEach(([i,n])=>s.add(new Option(n,i))),s.value=d??"",s}function et(t,d){const s=typeof d=="string"&&d.startsWith("#"),i=W([["","- none -"],["primary","primary"],["success","success"],["danger","danger"],["warning","warning"],["info","info"],["neutral","neutral"],["__custom__","Custom\u2026"]],s?"__custom__":d||""),n=b("input",{type:"color",className:"form-input me-colour-hex",value:s?d:"#000000"});n.style.display=s?"":"none",i.addEventListener("change",()=>{n.style.display=i.value==="__custom__"?"":"none"});const o=O(t,i);return o.append(n),o._read=()=>i.value==="__custom__"?n.value:i.value,o}function It(t,d){const s=b("input",{className:"form-input me-icon",name:"icon",value:d||""}),i=b("span",{className:"me-icon-preview"});d&&i.setAttribute("data-icon",d);const n=b("button",{type:"button",className:"btn btn-sm btn-ghost",textContent:"Browse\u2026"}),o=r=>{r?i.setAttribute("data-icon",r):i.removeAttribute("data-icon"),Domma.icons.scan(i.parentNode||i)};return n.addEventListener("click",()=>gt(r=>{s.value=r,o(r)})),s.addEventListener("input",()=>o(s.value.trim())),O(t,b("div",{className:"me-icon-field"},s,n,i))}function nt(...t){return b("div",{className:"me-field-grid"},...t)}async function jt(t,d,s,i,n){const o=u=>b("h4",{className:"me-form-section",textContent:u}),r=O("Label",b("input",{className:"form-input",name:"text",value:t.text||""}),"The text shown for this item."),y=O("URL",b("input",{className:"form-input",name:"url",value:t.url||""}),"Where it links. Must start with /, #, https://, or mailto: . Leave blank for a parent that only groups sub-items."),x=It("Icon",t.icon||""),v=O("Visibility",St(t.visibility||"",i),"Who can see this item, by role seniority. Picking a role shows it to that role and anyone more senior. Blank = everyone."),P=W([["","- no permission gate -"],...(d||[]).map(u=>[u.key,u.label||u.key])],t.permission||"");P.name="permission";const z=O("Permission",P,"Hide unless the viewer holds this specific permission. Independent of Visibility - both must pass."),it=O("Badge text",b("input",{className:"form-input",name:"badgeText",value:t.badge?.text||""})),U=W([["","- none -"],...(s||[]).map(u=>[u.slug,u.title||u.slug])],t.badge?.countFrom||"");U.name="badgeCount";const J=O("Badge count from",U,"Show a live count of this collection\u2019s entries as the badge."),G=et("Badge colour",t.badge?.variant||""),l=W([["link","Link"],["pill","Pill"]],t.pill?"pill":"link");l.name="renderAs";const w=O("Render as",l,"Pill draws the item as a rounded chip. Pill styling applies to top-level navbar items."),g=O("Pill style",W([["filled","Filled"],["outline","Outline"]],t.pill?.style||"filled"));g.querySelector("select").name="pillStyle";const M=et("Pill colour",t.pill?.variant||""),R=b("div",{className:"me-pill-wrap"},nt(g,M));R.style.display=t.pill?"":"none",l.addEventListener("change",()=>{R.style.display=l.value==="pill"?"":"none"});const T=et("Text colour",t.colour||""),q=b("button",{className:"btn btn-primary",textContent:"Apply"}),C=b("div",{className:"me-item-form"},o("Link"),r,y,x,o("Access"),nt(v,z),o("Badge"),nt(it,G),J,o("Appearance"),w,R,T,q),S=E.slideover({title:"Edit menu item",size:"lg",position:"right"});S.element.appendChild(C),S.open();const B=S.element.closest(".dm-slideover")||S.element.querySelector(".dm-slideover")||S.element;B.style.setProperty("width",wt+"px","important"),B.style.setProperty("max-width","95vw","important"),Domma.icons.scan(C);const A=u=>C.querySelector(`[name="${u}"]`)?.value||"";q.addEventListener("click",()=>{t.text=A("text"),t.url=A("url"),t.icon=A("icon"),t.visibility=A("visibility"),t.permission=A("permission");const u=A("badgeText"),N=A("badgeCount"),Y=G._read();if(t.badge=u||N?{...u&&{text:u},...N&&{countFrom:N},...Y&&{variant:Y}}:null,A("renderAs")==="pill"){const L=M._read();t.pill={style:A("pillStyle")||"filled",...L&&{variant:L}}}else t.pill=null;t.colour=T._read(),S.close(),n()})}export const menuEditorView={templateUrl:"/admin/js/templates/menu-editor.html",async onMount(t){const d=location.hash.match(/^#\/menus\/edit\/(.+)$/),s=d?decodeURIComponent(d[1]):null,i=!s,n=E.loader(t.get(0),{type:"dots"});let o,r,y,x,v,P,z;try{if(i)o={slug:"",name:"",description:"",items:[]};else try{o=await _.menus.get(s)}catch(e){E.toast(`Failed to load menu: ${e.message||e}`,{type:"error"}),location.hash="#/menus";return}r=await _.projects.list().catch(()=>[]),y=await H.get("/api/auth/permissions-registry").catch(()=>({resources:[]})),x=await _.collections.list().catch(()=>[]),v=await _.get("/collections/roles/entries?limit=100").catch(()=>null),P=await _.menuLocations.registry().catch(()=>[]),z=await _.pages.list().catch(()=>[])}finally{n.destroy()}const U=(Array.isArray(v)?v:v?.entries||[]).map(e=>({name:e.data?.name,label:e.data?.label,level:e.data?.level??99})).filter(e=>e.name).sort((e,a)=>a.level-e.level),J='<option value="">- no permission gate -</option>',G=e=>J+(y.resources||[]).map(a=>{const c=(e||"")===a.key?" selected":"";return`<option value="${a.key}"${c}>${a.label||a.key}</option>`}).join("");t.find("#me-title").text(i?"New menu":`Edit "${o.slug}"`);let l=lt(o.items);const w=t.find("#me-items-list");function g(){At(w,l,G),_t(w,()=>l,g)}g(),t.find("#me-add-item").on("click",()=>{j(w,l),l.push({id:D(),parentId:null,text:"",url:"/",icon:"",visibility:"",permission:"",hidden:!1,bundled:!1}),g()}),t.find("#me-add-separator").on("click",()=>{j(w,l),l.push({id:D(),parentId:null,type:"separator"}),g()}),t.find("#me-add-spacer").on("click",()=>{j(w,l),l.push({id:D(),parentId:null,type:"spacer",size:"md"}),g()});function M(e){return function(){j(w,l);const a=$(this).closest(".menu-tree-row").data("id"),c=l.findIndex(k=>k.id===a);if(c===-1)return;const f=l[c],p={separator:{type:"separator"},spacer:{type:"spacer",size:"md"}}[e],m=p?{id:D(),parentId:f.parentId,...p}:{id:D(),parentId:f.parentId,text:"",url:"/",icon:"",visibility:"",permission:"",hidden:!1,bundled:!1};l.splice(c+1,0,m),g()}}t.off("click",".me-ins-item").on("click",".me-ins-item",M("item")),t.off("click",".me-ins-sep").on("click",".me-ins-sep",M("separator")),t.off("click",".me-remove").on("click",".me-remove",function(){const e=$(this).closest(".menu-tree-row").data("id");l=l.filter(a=>a.id!==e&&a.parentId!==e),g()}),t.off("click",".me-indent").on("click",".me-indent",function(){j(w,l);const e=$(this).closest(".menu-tree-row").data("id"),a=l.find(p=>p.id===e);if(!a)return;const c=l.filter(p=>p.parentId===a.parentId),f=c.findIndex(p=>p.id===e);f<=0||["separator","spacer"].includes(c[f-1].type)||(a.parentId=c[f-1].id,g())}),t.off("click",".me-outdent").on("click",".me-outdent",function(){j(w,l);const e=$(this).closest(".menu-tree-row").data("id"),a=l.find(f=>f.id===e);if(!a||a.parentId==null)return;const c=l.find(f=>f.id===a.parentId);a.parentId=c?c.parentId:null,g()}),t.off("click",".me-up").on("click",".me-up",function(){j(w,l);const e=$(this).closest(".menu-tree-row").data("id"),a=l.filter(m=>m.parentId===l.find(k=>k.id===e)?.parentId),c=a.findIndex(m=>m.id===e);if(c<=0)return;const f=l.indexOf(a[c]),p=l.indexOf(a[c-1]);[l[f],l[p]]=[l[p],l[f]],g()}),t.off("click",".me-down").on("click",".me-down",function(){j(w,l);const e=$(this).closest(".menu-tree-row").data("id"),a=l.filter(m=>m.parentId===l.find(k=>k.id===e)?.parentId),c=a.findIndex(m=>m.id===e);if(c===-1||c>=a.length-1)return;const f=l.indexOf(a[c]),p=l.indexOf(a[c+1]);[l[f],l[p]]=[l[p],l[f]],g()}),t.off("click",".me-hidden").on("click",".me-hidden",function(){j(w,l);const e=$(this).closest(".menu-tree-row").data("id"),a=l.find(c=>c.id===e);a&&(a.hidden=!a.hidden),g()}),t.off("click",".me-edit").on("click",".me-edit",function(){const e=$(this).closest(".menu-tree-row").data("id"),a=l.find(c=>c.id===e);!a||["separator","spacer"].includes(a.type)||jt(a,y.resources||[],x,U,()=>g())}),E.tabs(t.find("#me-tabs").get(0)),t.find("#me-variant").val(o.variant||""),t.find("#me-position").val(o.position||"");const R=t.find("#me-appearOnHover").get(0);R&&(R.checked=o.appearOnHover===!0),t.find("#me-float-anchor").val(o.float?.anchor||"TL"),t.find("#me-float-offsetX").val(o.float?.offsetX??""),t.find("#me-float-offsetY").val(o.float?.offsetY??""),t.find("#me-float-width").val(o.float?.width??""),t.find("#me-float-radius").val(o.float?.radius??""),t.find("#me-float-shadow").val(o.float?.shadow||""),t.find("#me-float-opacity").val(o.float?.opacity??""),t.find("#me-float-title").val(o.float?.title||"");for(const[e,a]of[["#me-float-collapsible",o.float?.collapsible],["#me-float-collapsed",o.float?.collapsed],["#me-float-movable",o.float?.movable],["#me-float-resizable",o.float?.resizable]]){const c=t.find(e).get(0);c&&(c.checked=a===!0)}const T=o.float?.accent||"",q=T.startsWith("#"),C=t.find("#me-float-accent"),S=t.find("#me-float-accent-hex");C.val(q?"__custom__":T),q&&S.val(T);const B=()=>{S.get(0).style.display=C.val()==="__custom__"?"":"none"};B(),C.on("change",B);const A=()=>C.val()==="__custom__"?S.val():C.val(),u=()=>{const e=t.find("#me-position").val()==="floating";t.find(".dm-float-field").each(function(){this.style.display=e?"":"none"}),e&&B();const a=t.find("#me-orientation").val()==="vertical";t.find(".dm-vertical-field").each(function(){this.style.display=a?"":"none"});const c=!!(t.find("#me-float-collapsible").get(0)||{}).checked;t.find(".dm-float-collapsed-field").each(function(){this.style.display=e&&c?"":"none"})};t.find("#me-orientation").val(o.orientation||""),t.find("#me-side").val(o.side||"left"),u(),t.find("#me-position").on("change",u),t.find("#me-orientation").on("change",u),t.find("#me-float-collapsible").on("change",u),t.find("#me-highlight-hover").val(o.highlight?.hover||""),t.find("#me-highlight-active").val(o.highlight?.active||"");const N=o.highlight?.accent||"",Y=N.startsWith("#"),L=t.find("#me-highlight-accent"),Q=t.find("#me-highlight-accent-hex");L.val(Y?"__custom__":N),Y&&Q.val(N);const ot=()=>{Q.get(0).style.display=L.val()==="__custom__"?"":"none"};ot(),L.on("change",ot);const rt=()=>L.val()==="__custom__"?Q.val():L.val();t.find("#me-fontFamily").val(o.style?.fontFamily||""),t.find("#me-fontSize").val(o.style?.fontSize||""),t.find("#me-fontWeight").val(o.style?.fontWeight||""),t.find("#me-letterSpacing").val(o.style?.letterSpacing||""),t.find("#me-iconSize").val(o.style?.iconSize||"");const K=t.find("#me-bind-slot"),Z=t.find("#me-bind-projects"),X=t.find("#me-bind-pages");K.html('<option value="">- not bound -</option>'+(P||[]).map(e=>`<option value="${h(e.slot)}">${h(e.label||e.slot)} (${h(e.slot)})</option>`).join("")),Z.html(r.map(e=>`<option value="${h(e.slug)}">${h(e.name||e.slug)}</option>`).join("")),K.val(o.binding?.slot||"");const pt=new Set(o.binding?.projects||[]);Array.from(Z.get(0).options).forEach(e=>{e.selected=pt.has(e.value)}),X.val((o.binding?.pages||[]).join(`
|
|
44
|
-
`)),t.find("#me-bind-page-pick").html('<option value="">- pick a page -</option>'+
|
|
45
|
-
`).map(c=>c.trim()).filter(Boolean);a.includes(e)||a.push(e),
|
|
46
|
-
`)),
|
|
47
|
-
`).map(a=>a.trim()).filter(Boolean)}:null}function
|
|
43
|
+
`),i(o.id)}}i(null),Domma.icons.scan(t.get(0)),t.get(0).querySelectorAll("[data-tooltip]").forEach(n=>{E.tooltip(n,{content:n.getAttribute("data-tooltip"),position:"top"})})}function j(t,d){t.find(".menu-tree-row").each(function(){const s=$(this).data("id"),i=d.find(y=>y.id===s);if(!i||i.type==="separator")return;if(i.type==="spacer"){const y=$(this).find(".me-spacer-size");y.length&&(i.size=y.val());return}const n=$(this).find(".me-text");n.length&&(i.text=n.val());const o=$(this).find(".me-url");o.length&&(i.url=o.val());const p=$(this).find(".me-icon");p.length&&(i.icon=p.val());const w=$(this).find(".me-vis");w.length&&(i.visibility=w.val());const A=$(this).find(".me-perm");A.length&&(i.permission=A.val())})}function Nt(t,d){const s={};for(const i of d){const n=t.querySelector(`[name="${i}"]`);n&&(s[i]=n.type==="checkbox"?n.checked:n.value)}return s}function b(t,d={},...s){const i=document.createElement(t);Object.assign(i,d);for(const n of s)n!=null&&i.append(n);return i}function I(t,d,s){const i=b("label",{className:"form-field"},b("span",{className:"form-label",textContent:t}),d);return s&&i.append(b("small",{className:"form-hint",textContent:s,style:"display:block;font-size:.78em;opacity:.7;margin-top:.25rem;line-height:1.35"})),i}function Lt(t,d){const s=[["","- Everyone -"],["public","Public - anyone, incl. logged-out"],...(d||[]).map(n=>[n.name,`${n.label||n.name} and above`]),["private","Private - super-admin only"]];t&&!s.some(([n])=>n===t)&&s.push([t,`${t} (custom)`]);const i=P(s,t||"");return i.name="visibility",i}function P(t,d){const s=b("select",{className:"form-select"});return t.forEach(([i,n])=>s.add(new Option(n,i))),s.value=d??"",s}function it(t,d){const s=typeof d=="string"&&d.startsWith("#"),i=P([["","- none -"],["primary","primary"],["success","success"],["danger","danger"],["warning","warning"],["info","info"],["neutral","neutral"],["__custom__","Custom\u2026"]],s?"__custom__":d||""),n=b("input",{type:"color",className:"form-input me-colour-hex",value:s?d:"#000000"});n.style.display=s?"":"none",i.addEventListener("change",()=>{n.style.display=i.value==="__custom__"?"":"none"});const o=I(t,i);return o.append(n),o._read=()=>i.value==="__custom__"?n.value:i.value,o}function zt(t,d){const s=b("input",{className:"form-input me-icon",name:"icon",value:d||""}),i=b("span",{className:"me-icon-preview"});d&&i.setAttribute("data-icon",d);const n=b("button",{type:"button",className:"btn btn-sm btn-ghost",textContent:"Browse\u2026"}),o=p=>{p?i.setAttribute("data-icon",p):i.removeAttribute("data-icon"),Domma.icons.scan(i.parentNode||i)};return n.addEventListener("click",()=>At(p=>{s.value=p,o(p)})),s.addEventListener("input",()=>o(s.value.trim())),I(t,b("div",{className:"me-icon-field"},s,n,i))}function ot(...t){return b("div",{className:"me-field-grid"},...t)}async function Ct(t,d,s,i,n){const o=m=>b("h4",{className:"me-form-section",textContent:m}),p=I("Label",b("input",{className:"form-input",name:"text",value:t.text||""}),"The text shown for this item."),w=I("URL",b("input",{className:"form-input",name:"url",value:t.url||""}),"Where it links. Must start with /, #, https://, or mailto: . Leave blank for a parent that only groups sub-items."),A=zt("Icon",t.icon||""),y=I("Visibility",Lt(t.visibility||"",i),"Who can see this item, by role seniority. Picking a role shows it to that role and anyone more senior. Blank = everyone."),N=P([["","- no permission gate -"],...(d||[]).map(m=>[m.key,m.label||m.key])],t.permission||"");N.name="permission";const L=I("Permission",N,"Hide unless the viewer holds this specific permission. Independent of Visibility - both must pass."),lt=I("Badge text",b("input",{className:"form-input",name:"badgeText",value:t.badge?.text||""})),J=P([["","- none -"],...(s||[]).map(m=>[m.slug,m.title||m.slug])],t.badge?.countFrom||"");J.name="badgeCount";const at=I("Badge count from",J,"Show a live count of this collection\u2019s entries as the badge."),Q=it("Badge colour",t.badge?.variant||""),l=P([["link","Link"],["pill","Pill"]],t.pill?"pill":"link");l.name="renderAs";const x=I("Render as",l,"Pill draws the item as a rounded chip. Pill styling applies to top-level navbar items."),v=I("Pill style",P([["filled","Filled"],["outline","Outline"]],t.pill?.style||"filled"));v.querySelector("select").name="pillStyle";const Y=it("Pill colour",t.pill?.variant||""),W=b("div",{className:"me-pill-wrap"},ot(v,Y));W.style.display=t.pill?"":"none",l.addEventListener("change",()=>{W.style.display=l.value==="pill"?"":"none"});const D=it("Text colour",t.colour||""),X=P([["","- use the menu default -"],...rt.map(m=>[m,mt[m]||m])],t.highlight?.hover||"");X.name="hlHover";const R=I("Hover effect",X,"Overrides the menu-wide hover effect for this item only."),M=P([["","- use the menu default -"],...pt.map(m=>[m,ft[m]||m])],t.highlight?.active||"");M.name="hlActive";const U=I("When selected",M,"How this item is marked when it points at the page being viewed."),tt=it("Highlight colour",t.highlight?.accent||""),B=b("button",{className:"btn btn-primary",textContent:"Apply"}),V=b("div",{className:"me-item-form"},o("Link"),p,w,A,o("Access"),ot(y,L),o("Badge"),ot(lt,Q),at,o("Appearance"),x,W,D,o("Highlighting"),ot(R,U),tt,B),S=E.slideover({title:"Edit menu item",size:"lg",position:"right"});S.element.appendChild(V),S.open();const K=S.element.closest(".dm-slideover")||S.element.querySelector(".dm-slideover")||S.element;K.style.setProperty("width",It+"px","important"),K.style.setProperty("max-width","95vw","important"),Domma.icons.scan(V);const g=m=>V.querySelector(`[name="${m}"]`)?.value||"";B.addEventListener("click",()=>{t.text=g("text"),t.url=g("url"),t.icon=g("icon"),t.visibility=g("visibility"),t.permission=g("permission");const m=g("badgeText"),q=g("badgeCount"),et=Q._read();if(t.badge=m||q?{...m&&{text:m},...q&&{countFrom:q},...et&&{variant:et}}:null,g("renderAs")==="pill"){const nt=Y._read();t.pill={style:g("pillStyle")||"filled",...nt&&{variant:nt}}}else t.pill=null;t.colour=D._read();const F=g("hlHover"),T=g("hlActive"),z=tt._read();t.highlight=F||T||z?{...F&&{hover:F},...T&&{active:T},...z&&{accent:z}}:null,S.close(),n()})}export const menuEditorView={templateUrl:"/admin/js/templates/menu-editor.html",async onMount(t){const d=location.hash.match(/^#\/menus\/edit\/(.+)$/),s=d?decodeURIComponent(d[1]):null,i=!s,n=E.loader(t.get(0),{type:"dots"});let o,p,w,A,y,N,L;try{if(i)o={slug:"",name:"",description:"",items:[]};else try{o=await k.menus.get(s)}catch(e){E.toast(`Failed to load menu: ${e.message||e}`,{type:"error"}),location.hash="#/menus";return}p=await k.projects.list().catch(()=>[]),w=await H.get("/api/auth/permissions-registry").catch(()=>({resources:[]})),A=await k.collections.list().catch(()=>[]),y=await k.get("/collections/roles/entries?limit=100").catch(()=>null),N=await k.menuLocations.registry().catch(()=>[]),L=await k.pages.list().catch(()=>[])}finally{n.destroy()}const J=(Array.isArray(y)?y:y?.entries||[]).map(e=>({name:e.data?.name,label:e.data?.label,level:e.data?.level??99})).filter(e=>e.name).sort((e,a)=>a.level-e.level),at='<option value="">- no permission gate -</option>',Q=e=>at+(w.resources||[]).map(a=>{const c=(e||"")===a.key?" selected":"";return`<option value="${a.key}"${c}>${a.label||a.key}</option>`}).join("");t.find("#me-title").text(i?"New menu":`Edit "${o.slug}"`);let l=ut(o.items);const x=t.find("#me-items-list");function v(){jt(x,l,Q),Ot(x,()=>l,v)}v(),t.find("#me-add-item").on("click",()=>{j(x,l),l.push({id:G(),parentId:null,text:"",url:"/",icon:"",visibility:"",permission:"",hidden:!1,bundled:!1}),v()}),t.find("#me-add-separator").on("click",()=>{j(x,l),l.push({id:G(),parentId:null,type:"separator"}),v()}),t.find("#me-add-spacer").on("click",()=>{j(x,l),l.push({id:G(),parentId:null,type:"spacer",size:"md"}),v()});function Y(e){return function(){j(x,l);const a=$(this).closest(".menu-tree-row").data("id"),c=l.findIndex(_=>_.id===a);if(c===-1)return;const u=l[c],r={separator:{type:"separator"},spacer:{type:"spacer",size:"md"}}[e],f=r?{id:G(),parentId:u.parentId,...r}:{id:G(),parentId:u.parentId,text:"",url:"/",icon:"",visibility:"",permission:"",hidden:!1,bundled:!1};l.splice(c+1,0,f),v()}}t.off("click",".me-ins-item").on("click",".me-ins-item",Y("item")),t.off("click",".me-ins-sep").on("click",".me-ins-sep",Y("separator")),t.off("click",".me-remove").on("click",".me-remove",function(){const e=$(this).closest(".menu-tree-row").data("id");l=l.filter(a=>a.id!==e&&a.parentId!==e),v()}),t.off("click",".me-indent").on("click",".me-indent",function(){j(x,l);const e=$(this).closest(".menu-tree-row").data("id"),a=l.find(r=>r.id===e);if(!a)return;const c=l.filter(r=>r.parentId===a.parentId),u=c.findIndex(r=>r.id===e);u<=0||["separator","spacer"].includes(c[u-1].type)||(a.parentId=c[u-1].id,v())}),t.off("click",".me-outdent").on("click",".me-outdent",function(){j(x,l);const e=$(this).closest(".menu-tree-row").data("id"),a=l.find(u=>u.id===e);if(!a||a.parentId==null)return;const c=l.find(u=>u.id===a.parentId);a.parentId=c?c.parentId:null,v()}),t.off("click",".me-up").on("click",".me-up",function(){j(x,l);const e=$(this).closest(".menu-tree-row").data("id"),a=l.filter(f=>f.parentId===l.find(_=>_.id===e)?.parentId),c=a.findIndex(f=>f.id===e);if(c<=0)return;const u=l.indexOf(a[c]),r=l.indexOf(a[c-1]);[l[u],l[r]]=[l[r],l[u]],v()}),t.off("click",".me-down").on("click",".me-down",function(){j(x,l);const e=$(this).closest(".menu-tree-row").data("id"),a=l.filter(f=>f.parentId===l.find(_=>_.id===e)?.parentId),c=a.findIndex(f=>f.id===e);if(c===-1||c>=a.length-1)return;const u=l.indexOf(a[c]),r=l.indexOf(a[c+1]);[l[u],l[r]]=[l[r],l[u]],v()}),t.off("click",".me-hidden").on("click",".me-hidden",function(){j(x,l);const e=$(this).closest(".menu-tree-row").data("id"),a=l.find(c=>c.id===e);a&&(a.hidden=!a.hidden),v()}),t.off("click",".me-edit").on("click",".me-edit",function(){const e=$(this).closest(".menu-tree-row").data("id"),a=l.find(c=>c.id===e);!a||["separator","spacer"].includes(a.type)||Ct(a,w.resources||[],A,J,()=>v())}),E.tabs(t.find("#me-tabs").get(0)),t.find("#me-variant").val(o.variant||""),t.find("#me-position").val(o.position||"");const W=t.find("#me-appearOnHover").get(0);W&&(W.checked=o.appearOnHover===!0),t.find("#me-float-anchor").val(o.float?.anchor||"TL"),t.find("#me-float-offsetX").val(o.float?.offsetX??""),t.find("#me-float-offsetY").val(o.float?.offsetY??""),t.find("#me-float-width").val(o.float?.width??""),t.find("#me-float-radius").val(o.float?.radius??""),t.find("#me-float-shadow").val(o.float?.shadow||""),t.find("#me-float-opacity").val(o.float?.opacity??""),t.find("#me-float-title").val(o.float?.title||"");for(const[e,a]of[["#me-float-collapsible",o.float?.collapsible],["#me-float-collapsed",o.float?.collapsed],["#me-float-movable",o.float?.movable],["#me-float-resizable",o.float?.resizable]]){const c=t.find(e).get(0);c&&(c.checked=a===!0)}const D=o.float?.accent||"",X=D.startsWith("#"),R=t.find("#me-float-accent"),M=t.find("#me-float-accent-hex");R.val(X?"__custom__":D),X&&M.val(D);const U=()=>{M.get(0).style.display=R.val()==="__custom__"?"":"none"};U(),R.on("change",U);const tt=()=>R.val()==="__custom__"?M.val():R.val(),B=()=>{const e=t.find("#me-position").val()==="floating";t.find(".dm-float-field").each(function(){this.style.display=e?"":"none"}),e&&U();const a=t.find("#me-orientation").val()==="vertical";t.find(".dm-vertical-field").each(function(){this.style.display=a?"":"none"});const c=!!(t.find("#me-float-collapsible").get(0)||{}).checked;t.find(".dm-float-collapsed-field").each(function(){this.style.display=e&&c?"":"none"})};t.find("#me-orientation").val(o.orientation||""),t.find("#me-align").val(o.align||""),t.find("#me-side").val(o.side||"left"),B(),t.find("#me-position").on("change",B),t.find("#me-orientation").on("change",B),t.find("#me-float-collapsible").on("change",B);const V=(e,a,c,u)=>{const r=t.find(e).get(0);r&&(r.innerHTML="",r.add(new Option(u,"")),a.forEach(f=>r.add(new Option(c[f]||f,f))))};V("#me-highlight-hover",rt,mt,"- theme default -"),V("#me-highlight-active",pt,ft,"- theme default -"),t.find("#me-highlight-hover").val(o.highlight?.hover||""),t.find("#me-highlight-active").val(o.highlight?.active||"");const S=o.highlight?.accent||"",K=S.startsWith("#"),g=t.find("#me-highlight-accent"),m=t.find("#me-highlight-accent-hex");g.val(K?"__custom__":S),K&&m.val(S);const q=()=>{m.get(0).style.display=g.val()==="__custom__"?"":"none"};q(),g.on("change",q);const et=()=>g.val()==="__custom__"?m.val():g.val();t.find("#me-fontFamily").val(o.style?.fontFamily||""),t.find("#me-fontSize").val(o.style?.fontSize||""),t.find("#me-fontWeight").val(o.style?.fontWeight||""),t.find("#me-letterSpacing").val(o.style?.letterSpacing||""),t.find("#me-iconSize").val(o.style?.iconSize||"");const F=t.find("#me-bind-slot"),T=t.find("#me-bind-projects"),z=t.find("#me-bind-pages");F.html('<option value="">- not bound -</option>'+(N||[]).map(e=>`<option value="${h(e.slot)}">${h(e.label||e.slot)} (${h(e.slot)})</option>`).join("")),T.html(p.map(e=>`<option value="${h(e.slug)}">${h(e.name||e.slug)}</option>`).join("")),F.val(o.binding?.slot||"");const nt=new Set(o.binding?.projects||[]);Array.from(T.get(0).options).forEach(e=>{e.selected=nt.has(e.value)}),z.val((o.binding?.pages||[]).join(`
|
|
44
|
+
`)),t.find("#me-bind-page-pick").html('<option value="">- pick a page -</option>'+L.map(e=>{const a=e.urlPath||e.url||"";return a?`<option value="${h(a)}">${h(e.title||a)} - ${h(a)}</option>`:""}).join("")),t.find("#me-bind-page-add").on("click",()=>{const e=t.find("#me-bind-page-pick").val();if(!e)return;const a=z.val().split(`
|
|
45
|
+
`).map(c=>c.trim()).filter(Boolean);a.includes(e)||a.push(e),z.val(a.join(`
|
|
46
|
+
`)),Z()});function dt(){const e=F.val();return e?{slot:e,projects:Array.from(T.get(0).selectedOptions).map(a=>a.value),pages:z.val().split(`
|
|
47
|
+
`).map(a=>a.trim()).filter(Boolean)}:null}function Z(){const e=dt(),a=t.find("#me-bind-summary");if(!e){a.html('<span class="text-muted">Not bound - this menu renders only where it is mapped or placed by a shortcode.</span>');return}const c=[];e.projects.length&&c.push(`pages in ${e.projects.map(f=>`<code>${h(f)}</code>`).join(", ")}`),e.pages.length&&c.push(`pages matching ${e.pages.map(f=>`<code>${h(f)}</code>`).join(", ")}`);const u=e.pages.filter(f=>!/^\/[^\s?#*]*(\/\*)?$|^\/\*$/.test(f)),r=u.length?`<div class="text-danger" style="margin-top:.4rem">Not valid page patterns: ${u.map(f=>`<code>${h(f)}</code>`).join(", ")}</div>`:"";a.html(c.length?`Takes over the <code>${h(e.slot)}</code> slot on ${c.join(" and ")}.${r}`:`<span class="text-warning">Slot <code>${h(e.slot)}</code> selected but no project or page chosen - the binding will never match.</span>`)}F.on("change",Z),T.on("change",Z),z.on("input",Z),Z();const{getProjectFromHash:bt}=await import("../lib/project-context.js"),vt=i?bt():null,ct=t.find("#me-project");ct.html('<option value="">- none -</option>'+p.map(e=>`<option value="${h(e.slug)}">${h(e.name||e.slug)}</option>`).join("")),t.find("#me-slug").val(o.slug||""),t.find("#me-name").val(o.name||""),t.find("#me-description").val(o.description||""),ct.val(o.meta?.project||vt||""),Domma.icons.scan(t.get(0)),t.find("#me-save").on("click",async()=>{j(x,l);const e={variant:t.find("#me-variant").val(),position:t.find("#me-position").val(),appearOnHover:!!(t.find("#me-appearOnHover").get(0)||{}).checked,floatAnchor:t.find("#me-float-anchor").val(),floatOffsetX:t.find("#me-float-offsetX").val(),floatOffsetY:t.find("#me-float-offsetY").val(),floatWidth:t.find("#me-float-width").val(),floatRadius:t.find("#me-float-radius").val(),floatShadow:t.find("#me-float-shadow").val(),floatAccent:tt(),floatOpacity:t.find("#me-float-opacity").val(),floatTitle:t.find("#me-float-title").val().trim(),floatCollapsible:!!(t.find("#me-float-collapsible").get(0)||{}).checked,floatCollapsed:!!(t.find("#me-float-collapsed").get(0)||{}).checked,floatMovable:!!(t.find("#me-float-movable").get(0)||{}).checked,floatResizable:!!(t.find("#me-float-resizable").get(0)||{}).checked,highlightHover:t.find("#me-highlight-hover").val(),highlightActive:t.find("#me-highlight-active").val(),highlightAccent:et(),orientation:t.find("#me-orientation").val(),align:t.find("#me-align").val(),side:t.find("#me-side").val(),fontFamily:t.find("#me-fontFamily").val(),fontSize:t.find("#me-fontSize").val(),fontWeight:t.find("#me-fontWeight").val(),letterSpacing:t.find("#me-letterSpacing").val(),iconSize:t.find("#me-iconSize").val()},a={slug:t.find("#me-slug").val().trim(),name:t.find("#me-name").val().trim(),description:t.find("#me-description").val().trim(),project:t.find("#me-project").val()},c=["fontFamily","fontSize","fontWeight","letterSpacing","iconSize"],u=Object.fromEntries(Object.entries(e).filter(([O,C])=>c.includes(O)&&C)),r={};if(e.position==="floating"){e.floatAnchor&&(r.anchor=e.floatAnchor);for(const[O,C]of[["offsetX",e.floatOffsetX],["offsetY",e.floatOffsetY],["width",e.floatWidth],["radius",e.floatRadius],["opacity",e.floatOpacity]])C!==""&&C!=null&&Number.isFinite(Number(C))&&(r[O]=Number(C));e.floatShadow&&(r.shadow=e.floatShadow),e.floatAccent&&(r.accent=e.floatAccent),e.floatTitle&&(r.title=e.floatTitle),e.floatCollapsible&&(r.collapsible=!0),e.floatCollapsible&&e.floatCollapsed&&(r.collapsed=!0),e.floatMovable&&(r.movable=!0),e.floatResizable&&(r.resizable=!0)}const f={};e.highlightHover&&(f.hover=e.highlightHover),e.highlightActive&&(f.active=e.highlightActive),e.highlightAccent&&(f.accent=e.highlightAccent);const _={slug:a.slug,name:a.name,description:a.description,...e.variant&&{variant:e.variant},...e.position&&{position:e.position},...e.orientation&&{orientation:e.orientation},...e.orientation==="vertical"&&e.side&&{side:e.side},...e.orientation!=="vertical"&&e.align&&{align:e.align},...Object.keys(r).length&&{float:r},...Object.keys(f).length&&{highlight:f},...e.appearOnHover&&{appearOnHover:!0},...Object.keys(u).length&&{style:u},binding:dt(),items:Ht(l),meta:{...o.meta||{},project:a.project||null}};try{if(i)await k.menus.create(_);else if(_.slug!==o.slug){await k.menus.create(_);const O=await k.menuLocations.get();let C=!1;for(const[yt,wt]of Object.entries(O))wt===o.slug&&(O[yt]=_.slug,C=!0);C&&await k.menuLocations.save(O),await k.menus.remove(o.slug)}else await k.menus.update(o.slug,_);E.toast("Saved.",{type:"success"}),location.hash="#/menus/edit/"+encodeURIComponent(_.slug)}catch(O){E.toast(`Save failed: ${O.message||O}`,{type:"error"})}})}};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{api as i}from"../api.js";export const pluginMarketplaceView={templateUrl:"/admin/js/templates/plugin-marketplace.html",async onMount(e){const a=E.loader(e.get(0),{type:"dots"});let t;try{t=await i.marketplace.catalogue()}catch(n){E.toast(`Failed to load marketplace: ${n.message}`,{type:"error"}),a.destroy();return}if(a.destroy(),!t.available){e.find("#marketplace-unavailable").show();return}e.find("#marketplace-available").show(),b(e,t.catalogue),Domma.icons.scan()}};function b(e,a){const t=e.find("#marketplace-grid").get(0);if(t){for(;t.firstChild;)t.removeChild(t.firstChild);if(!a||a.length===0){const n=document.createElement("p");n.className="text-muted",n.textContent="No plugins available in the marketplace.",t.appendChild(n);return}a.forEach(n=>{const u
|
|
1
|
+
import{api as i}from"../api.js";import{pluginAccent as U,pluginWatermark as $}from"../lib/plugin-accent.js";export const pluginMarketplaceView={templateUrl:"/admin/js/templates/plugin-marketplace.html",async onMount(e){const a=E.loader(e.get(0),{type:"dots"});let t;try{t=await i.marketplace.catalogue()}catch(n){E.toast(`Failed to load marketplace: ${n.message}`,{type:"error"}),a.destroy();return}if(a.destroy(),!t.available){e.find("#marketplace-unavailable").show();return}e.find("#marketplace-available").show(),b(e,t.catalogue),Domma.icons.scan()}};function b(e,a){const t=e.find("#marketplace-grid").get(0);if(t){for(;t.firstChild;)t.removeChild(t.firstChild);if(!a||a.length===0){const n=document.createElement("p");n.className="text-muted",n.textContent="No plugins available in the marketplace.",t.appendChild(n);return}a.forEach(n=>{const u=P(n,e);t.appendChild(u)})}}function I(e){if(!e||e===0||e==="0")return"Free";const a=parseFloat(e);return isNaN(a)||a===0?"Free":`\xA3${a.toFixed(2)}/mo`}function P(e,a){const{slug:t,version:n,displayName:u,description:w,price:D,entitled:k,installed:F}=e,s=document.createElement("div");s.className="card plugin-card",s.dataset.slug=t,s.style.setProperty("--plugin-accent",U(t)),s.appendChild($(e.icon));const o=document.createElement("div");o.className="card-body";const c=document.createElement("div");c.className="plugin-header";const h=document.createElement("div");h.className="plugin-icon";const x=document.createElement("span");x.dataset.icon="package",h.appendChild(x);const r=document.createElement("div");r.className="plugin-meta";const g=document.createElement("div");g.className="plugin-name",g.textContent=u||t;const f=document.createElement("div");f.className="plugin-version",f.textContent=`v${n}`,r.appendChild(g),r.appendChild(f);const d=document.createElement("div");d.style.cssText="display:flex;gap:.35rem;align-items:center;";const C=document.createElement("span");if(C.className="badge badge-outline",C.textContent=I(D),d.appendChild(C),k){const N=document.createElement("span");N.className="badge badge-success",N.textContent="Entitled",d.appendChild(N)}c.appendChild(h),c.appendChild(r),c.appendChild(d);const v=document.createElement("p");v.className="plugin-desc",v.textContent=w||"",o.appendChild(c),o.appendChild(v);const m=document.createElement("div");m.className="plugin-footer";const p=document.createElement("span");p.className="text-muted",p.style.fontSize=".8rem",p.textContent="Restart server after changes",m.appendChild(p);const y=document.createElement("div");y.className="plugin-footer-actions";const l=document.createElement("button");return l.className="btn btn-sm",F?(l.className+=" btn-ghost",l.textContent="Uninstall",l.onclick=()=>M(t,a)):k?(l.className+=" btn-primary",l.textContent="Install",l.onclick=()=>R(t,n,a)):(l.className+=" btn-ghost",l.textContent="Not entitled",l.disabled=!0),y.appendChild(l),m.appendChild(y),s.appendChild(o),s.appendChild(m),s}async function R(e,a,t){if(await E.confirm(`Install ${e} v${a}?`))try{await i.marketplace.install(e,a),E.toast(`${e} installed. Restart the server to activate.`,{type:"success"});const n=await i.marketplace.catalogue();b(t,n.catalogue),Domma.icons.scan()}catch(n){E.toast(`Install failed: ${n.message}`,{type:"error"})}}async function M(e,a){if(await E.confirm(`Uninstall ${e}? This will remove its files.`))try{await i.marketplace.uninstall(e),E.toast(`${e} uninstalled. Restart to apply.`,{type:"success"});const t=await i.marketplace.catalogue();b(a,t.catalogue),Domma.icons.scan()}catch(t){E.toast(`Uninstall failed: ${t.message}`,{type:"error"})}}
|
|
@@ -1,39 +1,41 @@
|
|
|
1
|
-
import{api as
|
|
2
|
-
<div class="card plugin-card" data-plugin="${e.name}">
|
|
1
|
+
import{api as g,getUser as k}from"../api.js";import{pluginMarketplaceView as w}from"./plugin-marketplace.js";import{pluginAccent as S,pluginWatermark as N}from"../lib/plugin-accent.js";import{reportRestart as L}from"../lib/server-restart.js";export const pluginsView={templateUrl:"/admin/js/templates/plugins.html",async onMount(a){a.find("#new-plugin-btn").on("click",()=>T(a));let c=!1;a.find('[data-tab="marketplace"]').on("click",async function(){if(c)return;const n=a.find("#marketplace-mount");try{const s=await H.get(w.templateUrl),u=new DOMParser().parseFromString(s,"text/html");for(;u.body.firstChild;)n.get(0).appendChild(u.body.firstChild);await w.onMount(n),c=!0}catch{E.toast("Failed to load marketplace.",{type:"error"})}}),E.tabs(a.find("#plugin-tabs").get(0));const i=E.loader(a.get(0),{type:"dots"});let e=[];try{e=await g.plugins.list()}catch(n){E.toast(`Failed to load plugins: ${n.message}`,{type:"error"})}if(i.destroy(),!e.length){a.find("#plugins-empty").show();return}y(a,e),Domma.icons.scan()}};function T(a){const c=E.slideover({title:"New Plugin",size:"sm",position:"right"}),i=document.createElement("div");i.style.cssText="padding:.25rem 0 .5rem;";const e=k(),n=document.createElement("div");F.create({displayName:{type:"string",label:"Display Name",placeholder:"e.g. Event Manager",required:!0},slug:{type:"string",label:"Slug",placeholder:"event-manager",required:!0,help:"Folder name under plugins/ - lowercase letters, digits, hyphens."},description:{type:"string",label:"Description",placeholder:"What does this plugin do?"},author:{type:"string",label:"Author"},icon:{type:"string",label:"Icon",placeholder:"box",help:"A Domma icon name (as used in data-icon)."}},{author:e?.name||e?.username||"",icon:"box"},{showSubmitButton:!1}).renderTo(n),i.appendChild(n);const s=document.createElement("p");s.className="text-muted",s.style.cssText="font-size:.8rem;margin:.5rem 0 0;",s.textContent="The plugin is scaffolded from the built-in template with server routes, an admin view and a CLAUDE.md, and enabled automatically. Restart the server to activate it.",i.appendChild(s);const l=document.createElement("div");l.style.cssText="display:flex;justify-content:flex-end;gap:1rem;margin-top:.75rem;";const u=document.createElement("button");u.className="btn btn-ghost",u.textContent="Cancel";const r=document.createElement("button");r.className="btn btn-primary",r.textContent="Create Plugin",l.appendChild(u),l.appendChild(r),i.appendChild(l),c.element.appendChild(i),c.open();const m=n.querySelector('input[name="displayName"]'),t=n.querySelector('input[name="slug"]');setTimeout(()=>m?.focus(),50);let p=!1;t?.addEventListener("input",()=>{p=!0}),m?.addEventListener("input",()=>{p||!t||(t.value=m.value.toLowerCase().trim().replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,"").slice(0,40))});async function f(){const d={};for(const b of["displayName","slug","description","author","icon"])d[b]=n.querySelector(`input[name="${b}"]`)?.value.trim()||"";if(!d.displayName||!d.slug){E.toast("Display name and slug are required.",{type:"error"});return}r.disabled=!0,r.textContent="Creating\u2026";try{await g.plugins.scaffold(d),c.close(),E.toast(`Plugin "${d.displayName}" created. Restart the server to activate it.`,{type:"success"});const b=await g.plugins.list();b.length&&(a.find("#plugins-empty").hide(),y(a,b))}catch(b){E.toast(b.message||"Failed to create plugin.",{type:"error"}),r.disabled=!1,r.textContent="Create Plugin"}}u.addEventListener("click",()=>c.close()),r.addEventListener("click",f),t?.addEventListener("keydown",d=>{d.key==="Enter"&&f()})}function B(a){if(!a)return"";const c=new Date(a);if(Number.isNaN(c.getTime()))return"";try{return D(a).format("DD.MM.YYYY")}catch{return""}}function y(a,c){const i=a.find("#plugins-grid").empty();c.forEach(e=>{const n=!!e.core,s=e.sourceEditable!==!1,l="var(--dm-warning,#d97706)",u=e.closedSource?`<span class="badge badge-outline" title="${o(e.licence||"Commercial licence")} - source is not editable here" style="font-size:0.65rem;padding:1px 6px;background:none;border:1px solid;color:${l};border-color:${l};">Licensed</span>`:"",r=n?"badge-info":e.enabled?"badge-success":"badge-secondary",m=n?"Built in":e.enabled?"Enabled":"Disabled",t=e.enabled?"Disable":"Enable",p=e.enabled?"btn-ghost":"btn-primary",f=e.bundled?"var(--dm-info,#2563eb)":"var(--dm-secondary,#6b7280)",d=e.bundled?"Bundled - click to unbundle":"Not bundled - click to bundle",b=`<button class="badge badge-outline btn-bundle-plugin" data-name="${e.name}" data-bundled="${e.bundled?"1":"0"}" title="${d}" style="font-size:0.65rem;padding:1px 6px;cursor:pointer;background:none;border:1px solid;color:${f};border-color:${f};">${e.bundled?"Bundled":"Unbundled"}</button>`,h=e.author?`by ${o(e.author)}`:"",v=B(e.date),x=e.icon||"package",C=`
|
|
2
|
+
<div class="card plugin-card" data-plugin="${e.name}" style="--plugin-accent:${S(e.name)}">
|
|
3
|
+
${N(x).outerHTML}
|
|
3
4
|
<div class="card-body">
|
|
4
5
|
<div class="plugin-header">
|
|
5
|
-
<div class="plugin-icon"><span data-icon="${
|
|
6
|
+
<div class="plugin-icon"><span data-icon="${x}"></span></div>
|
|
6
7
|
<div class="plugin-meta">
|
|
7
|
-
<div class="plugin-name">${
|
|
8
|
-
<div class="plugin-version">v${
|
|
8
|
+
<div class="plugin-name">${o(e.displayName)}</div>
|
|
9
|
+
<div class="plugin-version">v${o(e.version)}${h?` · ${h}`:""}</div>
|
|
9
10
|
</div>
|
|
10
|
-
<div style="display:flex;gap:.35rem;align-items:center;">
|
|
11
|
-
${
|
|
12
|
-
|
|
11
|
+
<div style="display:flex;gap:.35rem;align-items:center;flex-wrap:wrap;justify-content:flex-end;flex-shrink:0;margin-left:auto;">
|
|
12
|
+
${b}
|
|
13
|
+
${u}
|
|
14
|
+
<span class="badge ${r}">${m}</span>
|
|
13
15
|
</div>
|
|
14
16
|
</div>
|
|
15
|
-
<p class="plugin-desc">${
|
|
16
|
-
${
|
|
17
|
+
<p class="plugin-desc">${o(e.description)}</p>
|
|
18
|
+
${v?`<p class="plugin-date text-muted" style="font-size:.8rem;margin:0">Released ${v}</p>`:""}
|
|
17
19
|
</div>
|
|
18
20
|
<div class="plugin-footer">
|
|
19
|
-
<span class="text-muted" style="font-size:.8rem"
|
|
21
|
+
<span class="text-muted" style="font-size:.8rem">${n?"Part of the CMS - always on":"Restart server after changes"}</span>
|
|
20
22
|
<div class="plugin-footer-actions">
|
|
21
|
-
|
|
23
|
+
${s?`<button class="btn btn-sm btn-ghost btn-plugin-code" data-name="${e.name}"><span data-icon="code"></span> Code</button>`:""}
|
|
22
24
|
${e.settingsSchema?`<button class="btn btn-sm btn-ghost btn-plugin-settings" data-name="${e.name}"><span data-icon="settings"></span> Settings</button>`:""}
|
|
23
|
-
|
|
25
|
+
${n?"":`<button class="btn btn-sm ${p} btn-toggle-plugin"
|
|
24
26
|
data-name="${e.name}"
|
|
25
27
|
data-enabled="${e.enabled?"1":"0"}">
|
|
26
|
-
<span data-icon="${e.enabled?"pause":"play"}"></span> ${
|
|
27
|
-
</button
|
|
28
|
+
<span data-icon="${e.enabled?"pause":"play"}"></span> ${t}
|
|
29
|
+
</button>`}
|
|
28
30
|
</div>
|
|
29
31
|
</div>
|
|
30
|
-
</div>`;
|
|
31
|
-
<label class="form-label">${
|
|
32
|
-
<select class="form-control" name="${
|
|
32
|
+
</div>`;i.append(C)}),Domma.icons.scan(),Domma.effects.reveal(".plugin-card",{animation:"fade",stagger:60,duration:400}),Domma.effects.ripple(".btn-toggle-plugin"),i.on("click",".btn-plugin-code",function(){R.navigate(`/plugins/code/${$(this).data("name")}`)}),i.on("click",".btn-plugin-settings",async function(){const e=$(this).data("name"),n=c.find(t=>t.name===e);if(!n?.settingsSchema)return;const l=`<form id="plugin-settings-form">${n.settingsSchema.map(t=>{const p=n.settings?.[t.key]??t.default??"";if(t.type==="select"){const f=t.options.map(d=>`<option value="${o(d.value)}"${String(p)===String(d.value)?" selected":""}>${o(d.label)}</option>`).join("");return`<div class="form-group">
|
|
33
|
+
<label class="form-label">${o(t.label)}</label>
|
|
34
|
+
<select class="form-control" name="${o(t.key)}">${f}</select>
|
|
33
35
|
</div>`}return t.type==="number"?`<div class="form-group">
|
|
34
|
-
<label class="form-label">${
|
|
35
|
-
<input class="form-control" type="number" name="${
|
|
36
|
+
<label class="form-label">${o(t.label)}</label>
|
|
37
|
+
<input class="form-control" type="number" name="${o(t.key)}" value="${o(String(p))}"${t.min!=null?` min="${t.min}"`:""}${t.max!=null?` max="${t.max}"`:""}>
|
|
36
38
|
</div>`:`<div class="form-group">
|
|
37
|
-
<label class="form-label">${
|
|
38
|
-
<input class="form-control" type="text" name="${
|
|
39
|
-
</div>`}).join("")}</form>`;if(!await E.confirm(l,{title:`${
|
|
39
|
+
<label class="form-label">${o(t.label)}</label>
|
|
40
|
+
<input class="form-control" type="text" name="${o(t.key)}" value="${o(String(p))}">
|
|
41
|
+
</div>`}).join("")}</form>`;if(!await E.confirm(l,{title:`${o(n.displayName)} Settings`,confirmText:"Save",cancelText:"Cancel",html:!0}))return;const r=document.getElementById("plugin-settings-form");if(!r)return;const m={};n.settingsSchema.forEach(t=>{const p=r.elements[t.key];p&&(m[t.key]=t.type==="number"?Number(p.value):p.value)});try{await g.plugins.update(e,{settings:m}),n.settings={...n.settings,...m},E.toast("Settings saved.",{type:"success"})}catch(t){E.toast(`Failed to save: ${t.message}`,{type:"error"})}}),i.on("click",".btn-bundle-plugin",async function(){const e=$(this),n=e.data("name"),s=e.data("bundled")!==1&&e.data("bundled")!=="1";e.prop("disabled",!0).text(s?"Bundling\u2026":"Unbundling\u2026");try{await g.plugins.update(n,{bundled:s}),E.toast(`Plugin ${s?"marked as bundled":"unbundled"}.`,{type:"success"});const l=await g.plugins.list();y(a,l)}catch(l){E.toast(`Failed: ${l.message}`,{type:"error"}),e.prop("disabled",!1)}}),i.on("click",".btn-toggle-plugin",async function(){const e=$(this),n=e.data("name"),s=e.data("enabled")!==1&&e.data("enabled")!=="1";e.prop("disabled",!0).text(s?"Enabling\u2026":"Disabling\u2026");try{const l=await g.plugins.update(n,{enabled:s});E.toast(`Plugin ${s?"enabled":"disabled"}.`,{type:"success"}),await L(l,{onStatus:r=>e.text(r.slice(0,24))});const u=await g.plugins.list();y(a,u)}catch(l){E.toast(`Failed: ${l.message}`,{type:"error"}),e.prop("disabled",!1)}})}function o(a){return String(a).replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""")}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{apiRequest as q}from"/admin/js/api.js";export const searchView={templateUrl:"/admin/js/templates/search.html",async onMount(s){let n={};const A=E.loader(s.get(0),{type:"dots"});try{n=await q("/search/settings")}catch{E.toast("Could not load settings.",{type:"error"})}finally{A.destroy()}s.find("#field-enabled").prop("checked",n.enabled!==!1),s.find("#field-placeholder").val(n.placeholder||"Search pages..."),s.find("#field-keyboard-shortcut").prop("checked",n.keyboardShortcut!==!1),s.find("#field-max-results").val(n.maxResults??10),s.find("#field-min-query-length").val(n.minQueryLength??2),s.find("#field-debounce-ms").val(n.debounceMs??300);const g=n.displayMode||"navbar",d=document.getElementById("field-display-mode"),a=document.getElementById("field-position"),f=document.getElementById("field-offset-x"),u=document.getElementById("field-offset-y"),l=document.getElementById("field-icon-only"),o=document.getElementById("field-icon-radius"),c=document.getElementById("field-icon-size"),h=document.getElementById("ss-size-row"),v=document.getElementById("ss-size-value"),b=document.getElementById("ss-radius-max-hint"),I=document.getElementById("ss-radius-row"),x=document.getElementById("ss-radius-value"),r=document.getElementById("ss-radius-preview"),B=document.getElementById("ss-pos-section"),S=document.getElementById("ss-pos-grid"),p=document.getElementById("ss-display-mode");d&&(d.value=g),a&&(a.value=n.position??"bottom-right"),f&&(f.value=n.offsetX??32),u&&(u.value=n.offsetY??32),l&&(l.checked=n.iconOnly!==!1),c&&(c.value=n.iconSize??52),o&&(o.value=n.iconRadius??26);const k=()=>parseInt(c?.value,10)||52;function y(){const e=k(),t=parseInt(o?.value,10)||0;if(v&&(v.textContent=e+"px"),x&&(x.textContent=t+"px"),!r)return;r.style.width=e+"px",r.style.height=e+"px",r.style.borderRadius=t+"px";const i=Math.round(e*20/52),m=r.querySelector("svg, [data-icon]");m&&(m.style.width=i+"px",m.style.height=i+"px")}function w(){if(!o)return;const e=parseInt(o.max,10),t=Math.floor(k()/2),i=parseInt(o.value,10)||0;o.max=String(t),(i>=e||i>t)&&(o.value=String(t)),b&&(b.textContent=String(t))}function M(){const e=l?.checked?"":"none";I&&(I.style.display=e),h&&(h.style.display=e)}w(),y(),M(),o?.addEventListener("input",y),c?.addEventListener("input",()=>{w(),y()}),l?.addEventListener("change",M);function R(e){d&&(d.value=e),B&&(B.style.display=e==="floating"?"":"none"),p&&p.querySelectorAll(".ss-mode-btn").forEach(function(t){const i=t.getAttribute("data-mode")===e;t.classList.toggle("btn-primary",i),t.classList.toggle("btn-secondary",!i)})}function z(e){document.querySelectorAll(".ss-pos-btn").forEach(function(t){const i=t.getAttribute("data-pos")===e;t.style.background=i?"var(--dm-primary, #5b8cff)":"",t.style.color=i?"#fff":""})}R(g),z(n.position||"bottom-right"),p&&p.addEventListener("click",function(e){const t=e.target.closest(".ss-mode-btn");t&&R(t.getAttribute("data-mode"))}),S&&S.addEventListener("click",function(e){const t=e.target.closest("[data-pos]");if(!t)return;const i=t.getAttribute("data-pos");a&&(a.value=i),z(i)}),s.find("#save-settings-btn").off("click").on("click",async()=>{const e={enabled:s.find("#field-enabled").prop("checked"),placeholder:s.find("#field-placeholder").val()||"Search pages...",keyboardShortcut:s.find("#field-keyboard-shortcut").prop("checked"),maxResults:parseInt(s.find("#field-max-results").val(),10)||10,minQueryLength:parseInt(s.find("#field-min-query-length").val(),10)||2,debounceMs:parseInt(s.find("#field-debounce-ms").val(),10)||300,displayMode:d?d.value:"navbar",position:a?a.value:"bottom-right",offsetX:f&&parseInt(f.value,10)||32,offsetY:u&&parseInt(u.value,10)||32,iconOnly:l?l.checked:!0,iconSize:c&&parseInt(c.value,10)||52,iconRadius:o?parseInt(o.value,10)||0:26};try{await q("/search/settings",{method:"PUT",body:JSON.stringify(e)}),E.toast("Settings saved.",{type:"success"})}catch{E.toast("Failed to save settings.",{type:"error"})}});const L=s.find("#ss-settings-tabs").get(0);L&&E.tabs(L),Domma.icons.scan(),y()}};
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import{api as
|
|
2
|
-
`,
|
|
3
|
-
`,
|
|
1
|
+
import{api as v,apiRequest as b}from"../api.js";export const settingsView={templateUrl:"/admin/js/templates/settings.html",async onMount(e){E.tabs(e.find("#settings-tabs").get(0));const p=E.loader(e.get(0),{type:"dots"}),l=await v.settings.get().catch(()=>({}));p.destroy(),e.find("#field-site-title").val(l.title||""),e.find("#field-tagline").val(l.tagline||""),e.find("#field-spacer-size").val(l.layoutOptions?.spacerSize??40),e.find("#field-seo-title").val(l.seo?.defaultTitle||""),e.find("#field-seo-separator").val(l.seo?.titleSeparator||" | "),e.find("#field-seo-desc").val(l.seo?.defaultDescription||""),e.find("#field-footer-copy").val(l.footer?.copyright||""),e.find("#field-footer-motion-toggle").prop("checked",l.footer?.showMotionToggle!==!1),e.find("#field-social-twitter").val(l.social?.twitter||""),e.find("#field-social-facebook").val(l.social?.facebook||""),e.find("#field-social-instagram").val(l.social?.instagram||""),e.find("#field-social-linkedin").val(l.social?.linkedin||""),e.find("#field-social-github").val(l.social?.github||""),e.find("#field-social-youtube").val(l.social?.youtube||""),e.find("#field-smtp-host").val(l.smtp?.host||""),e.find("#field-smtp-port").val(l.smtp?.port||587),e.find("#field-smtp-user").val(l.smtp?.user||""),e.find("#field-smtp-pass").val(l.smtp?.pass||""),e.find("#field-smtp-secure").prop("checked",l.smtp?.secure||!1),e.find("#field-smtp-from-address").val(l.smtp?.fromAddress||""),e.find("#field-smtp-from-name").val(l.smtp?.fromName||""),y(l.smtp?.host||""),e.find("#field-smtp-host").on("input",function(){y(this.value)});const c=l.backToTop||{};e.find("#field-btt-enabled").prop("checked",c.enabled!==!1),e.find("#field-btt-threshold").val(c.scrollThreshold??300),e.find("#field-btt-position").val(c.position||"bottom-right"),e.find("#field-btt-offset").val(c.offset??32),e.find("#field-btt-bottom-offset").val(c.bottomOffset??c.offset??32),e.find("#field-btt-label").val(c.label||""),e.find("#field-btt-smooth").prop("checked",c.smooth!==!1);const i=l.cookieConsent||{};e.find("#field-cc-enabled").prop("checked",i.enabled!==!1),e.find("#field-cc-message").val(i.message||""),e.find("#field-cc-accept-all").val(i.acceptAllText||"Accept All"),e.find("#field-cc-reject-all").val(i.rejectAllText||"Reject All"),e.find("#field-cc-customize").val(i.customizeText||"Customize"),e.find("#field-cc-save-prefs").val(i.savePreferencesText||"Save Preferences"),e.find("#field-cc-privacy-text").val(i.privacyPolicyText||"Privacy Policy"),e.find("#field-cc-privacy-url").val(i.privacyPolicyUrl||""),e.find("#field-cc-cookie-text").val(i.cookiePolicyText||"Cookie Policy"),e.find("#field-cc-cookie-url").val(i.cookiePolicyUrl||""),e.find("#field-cc-position").val(i.position||"bottom"),e.find("#field-cc-layout").val(i.layout||"bar"),e.find("#field-cc-theme").val(i.theme||"dark"),e.find("#field-cc-show-functional").prop("checked",i.showFunctional!==!1),e.find("#field-cc-show-analytics").prop("checked",i.showAnalytics!==!1),e.find("#field-cc-show-marketing").prop("checked",i.showMarketing!==!1),e.find("#field-cc-version").val(i.consentVersion||"1.0");const m=l.breadcrumbs||{};e.find("#field-breadcrumbs-enabled").prop("checked",m.enabled===!0),e.find("#field-breadcrumbs-home-label").val(m.homeLabel||"Home"),e.find("#field-breadcrumbs-position").val(m.position||"TL"),e.find("#field-bc-offset-x").val(m.offsetX??8),e.find("#field-bc-offset-y").val(m.offsetY??8);function u(t){e.find(".bc-pos-btn").each(function(){const s=this.dataset.pos===t;this.style.background=s?"var(--dm-primary, #5b8cff)":"",this.style.color=s?"var(--dm-primary-text)":""})}u(m.position||"TL"),e.find(".bc-pos-btn").on("click",function(){const t=this.dataset.pos;e.find("#field-breadcrumbs-position").val(t),u(t)});let n=null;try{const{css:t}=await b("/settings/custom-css");e.find("#field-custom-css").val(t||""),E.editor&&(n=E.editor(e.find("#field-custom-css").get(0),{mode:"code",language:"css",lineNumbers:!0,showToolbar:!1,minHeight:420,placeholder:"/* Add your custom CSS here */",characterCount:!0}))}catch{}n&&n._editorEl&&n._editorEl.addEventListener("keydown",t=>{if(!t.ctrlKey&&!t.metaKey)return;if(t.key==="s"){t.preventDefault(),e.find("#save-css-btn").get(0)?.click();return}const s=n._editorEl,a=s.selectionStart!==s.selectionEnd;if((t.key==="c"||t.key==="x")&&!a){t.preventDefault();const d=s.value,o=s.selectionStart,r=d.lastIndexOf(`
|
|
2
|
+
`,o-1)+1,h=d.indexOf(`
|
|
3
|
+
`,o),g=h===-1?d.slice(r):d.slice(r,h+1);if(navigator.clipboard.writeText(g),t.key==="x"){const k=d.slice(0,r),x=h===-1?"":d.slice(h+1);s.value=k+x,s.selectionStart=s.selectionEnd=r,s.dispatchEvent(new Event("input",{bubbles:!0})),E.toast("Line cut",{type:"info",duration:1500})}else E.toast("Line copied",{type:"info",duration:1500})}}),e.find("#save-settings-btn").on("click",async()=>{const t={title:e.find("#field-site-title").val().trim(),tagline:e.find("#field-tagline").val().trim(),layoutOptions:{spacerSize:parseInt(e.find("#field-spacer-size").val(),10)||40},seo:{defaultTitle:e.find("#field-seo-title").val().trim(),titleSeparator:e.find("#field-seo-separator").val()||" | ",defaultDescription:e.find("#field-seo-desc").val().trim()},footer:{copyright:e.find("#field-footer-copy").val().trim(),showMotionToggle:e.find("#field-footer-motion-toggle").prop("checked"),links:l.footer?.links||[]},social:{twitter:e.find("#field-social-twitter").val().trim(),facebook:e.find("#field-social-facebook").val().trim(),instagram:e.find("#field-social-instagram").val().trim(),linkedin:e.find("#field-social-linkedin").val().trim(),github:e.find("#field-social-github").val().trim(),youtube:e.find("#field-social-youtube").val().trim()},smtp:{host:e.find("#field-smtp-host").val().trim(),port:parseInt(e.find("#field-smtp-port").val(),10)||587,user:e.find("#field-smtp-user").val().trim(),pass:e.find("#field-smtp-pass").val(),secure:e.find("#field-smtp-secure").prop("checked"),fromAddress:e.find("#field-smtp-from-address").val().trim(),fromName:e.find("#field-smtp-from-name").val().trim()},backToTop:{enabled:e.find("#field-btt-enabled").prop("checked"),scrollThreshold:parseInt(e.find("#field-btt-threshold").val(),10)||300,position:e.find("#field-btt-position").val()||"bottom-right",offset:parseInt(e.find("#field-btt-offset").val(),10)||32,bottomOffset:parseInt(e.find("#field-btt-bottom-offset").val(),10)||32,label:e.find("#field-btt-label").val().trim(),smooth:e.find("#field-btt-smooth").prop("checked")},cookieConsent:{enabled:e.find("#field-cc-enabled").prop("checked"),message:e.find("#field-cc-message").val().trim(),acceptAllText:e.find("#field-cc-accept-all").val().trim(),rejectAllText:e.find("#field-cc-reject-all").val().trim(),customizeText:e.find("#field-cc-customize").val().trim(),savePreferencesText:e.find("#field-cc-save-prefs").val().trim(),privacyPolicyText:e.find("#field-cc-privacy-text").val().trim(),privacyPolicyUrl:e.find("#field-cc-privacy-url").val().trim(),cookiePolicyText:e.find("#field-cc-cookie-text").val().trim(),cookiePolicyUrl:e.find("#field-cc-cookie-url").val().trim(),position:e.find("#field-cc-position").val(),layout:e.find("#field-cc-layout").val(),theme:e.find("#field-cc-theme").val(),showFunctional:e.find("#field-cc-show-functional").prop("checked"),showAnalytics:e.find("#field-cc-show-analytics").prop("checked"),showMarketing:e.find("#field-cc-show-marketing").prop("checked"),consentVersion:e.find("#field-cc-version").val().trim()||"1.0"}},s=e.find("#save-settings-btn");s.prop("disabled",!0);try{await v.settings.save(t),E.toast("Settings saved.",{type:"success"})}catch{E.toast("Failed to save settings.",{type:"error"})}finally{s.prop("disabled",!1)}}),e.find("#send-test-email-btn").on("click",async()=>{const t=e.find("#field-test-email-to").val().trim(),s=e.find("#test-email-result").get(0),a=e.find("#send-test-email-btn");a.prop("disabled",!0),s&&(s.textContent="Sending\u2026",s.style.color="");try{const d=await b("/settings/test-email",{method:"POST",body:JSON.stringify({to:t||void 0})});s&&(s.textContent=d.message||"Test email sent.",s.style.color="var(--dm-success,#4ade80)")}catch(d){s&&(s.textContent=d.message||"Failed to send test email.",s.style.color="var(--dm-danger,#f87171)")}finally{a.prop("disabled",!1)}}),e.find("#save-breadcrumbs-btn").on("click",async()=>{const t=e.find("#save-breadcrumbs-btn");t.prop("disabled",!0);try{const s={enabled:e.find("#field-breadcrumbs-enabled").prop("checked"),homeLabel:e.find("#field-breadcrumbs-home-label").val().trim()||"Home",position:e.find("#field-breadcrumbs-position").val()||"TL",offsetX:parseInt(e.find("#field-bc-offset-x").val(),10)||8,offsetY:parseInt(e.find("#field-bc-offset-y").val(),10)||8};await v.settings.save({breadcrumbs:s}),l.breadcrumbs=s,E.toast("Breadcrumbs settings saved.",{type:"success"})}catch{E.toast("Failed to save breadcrumbs settings.",{type:"error"})}finally{t.prop("disabled",!1)}}),e.find("#save-css-btn").on("click",async()=>{const t=n?n.getValue():e.find("#field-custom-css").val(),s=e.find("#save-css-btn");s.prop("disabled",!0);try{await b("/settings/custom-css",{method:"PUT",body:JSON.stringify({css:t})}),E.toast("Custom CSS saved.",{type:"success"})}catch(a){E.toast(a.message||"Failed to save CSS.",{type:"error"})}finally{s.prop("disabled",!1)}});async function f(){try{const t=await b("/cache/status"),s=t.enabled?`enabled (${t.driver})`:"disabled",a=t.maxItems?` \xB7 ${t.size} / ${t.maxItems} entries`:"";e.find("#cache-status").text(s+a)}catch{e.find("#cache-status").text("unknown")}try{const{entries:t}=await b("/cache/keys");w(e.find("#cache-keys-table"),t),e.find("#cache-key-count").text(`(${t.length})`)}catch(t){e.find("#cache-keys-table").get(0).textContent="Failed to load keys: "+t.message}}await f(),e.find("#btn-clear-cache").on("click",async()=>{if(!await E.confirm("Clear the entire response cache?"))return;const t=e.find("#btn-clear-cache");t.prop("disabled",!0);try{await b("/cache/clear",{method:"POST"}),E.toast("Cache cleared.",{type:"success"}),await f()}catch(s){E.toast(s.message||"Failed to clear cache.",{type:"error"})}finally{t.prop("disabled",!1)}}),e.find("#btn-refresh-cache-keys").on("click",f)}};function y(e){const p=document.getElementById("smtp-unconfigured-notice");p&&(p.style.display=String(e||"").trim()?"none":"",p.style.display===""&&Domma.icons.scan(p))}function w(e,p){const l=e.get(0);if(!l)return;for(;l.firstChild;)l.removeChild(l.firstChild);if(!p.length){const f=document.createElement("p");f.className="text-muted",f.style.fontSize=".875rem",f.textContent="Cache is empty.",l.appendChild(f);return}const c=[...p].reverse(),i=document.createElement("table");i.className="table table-compact";const m=document.createElement("thead"),u=document.createElement("tr");for(const f of["Key","Tags","Expires"]){const t=document.createElement("th");t.textContent=f,u.appendChild(t)}m.appendChild(u),i.appendChild(m);const n=document.createElement("tbody");for(const f of c){const t=document.createElement("tr"),s=document.createElement("td");s.style.fontFamily="monospace",s.style.fontSize="12px",s.textContent=f.key,t.appendChild(s);const a=document.createElement("td");a.style.fontSize="12px";for(const o of f.tags){const r=document.createElement("span");r.className="badge",r.style.marginRight="4px",r.style.fontSize="11px",r.textContent=o,a.appendChild(r)}t.appendChild(a);const d=document.createElement("td");if(d.style.fontSize="12px",d.style.color="var(--dm-text-muted, #6b7280)",f.expiresAt===null)d.textContent="no expiry";else{const o=f.expiresAt-Date.now();o<=0?d.textContent="expired":o<6e4?d.textContent=`${Math.round(o/1e3)}s`:o<36e5?d.textContent=`${Math.round(o/6e4)}m`:d.textContent=`${Math.round(o/36e5)}h`}t.appendChild(d),n.appendChild(t)}i.appendChild(n),l.appendChild(i)}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{api as T}from"../api.js";import{populateThemeSelect as w}from"../lib/themes.js";const se=[{value:"#site-navbar",label:"Navigation bar"},{value:"#site-footer",label:"Footer"},{value:".site-content",label:"Page content area"},{value:".page-body",label:"Page body (inside the container)"},{value:".site-sidebar",label:"Sidebar"},{value:".card",label:"Every card"},{value:".cms-form-embed",label:"Embedded forms"},{value:".cms-collection",label:"Collection displays"}];export const themeView={templateUrl:"/admin/js/templates/theme.html",async onMount(r){E.tabs(r.find("#theme-tabs").get(0));const F=E.loader(r.get(0),{type:"dots"});let d,R;try{[d,R]=await Promise.all([T.theme.get(),T.theme.catalog().catch(()=>({themes:[],tokens:[]}))])}catch(e){F.destroy(),E.toast(`Could not load theme settings: ${e.message||e}`,{type:"error"});return}F.destroy();const z=R.tokens||[],A=new Map;async function K(e){if(!e)return null;if(A.has(e))return A.get(e);const t=await T.theme.themeTokens(e).catch(()=>null),n=t&&t.tokens?t.tokens:null;return A.set(e,n),n}function $(){const e=r.find("#theme-auto-enabled").prop("checked");return d.baseTheme||(e?S.val():f.val())||d.theme}const V=["--dm-card-bg","--dm-surface"],_=[...V,"--dm-text","--dm-primary","--dm-background"];function H(e){const t=document.createElement("span");return t.style.cssText="display:inline-block;width:.7rem;height:.7rem;border-radius:2px;border:1px solid var(--dm-border);vertical-align:middle;margin-right:.25rem;",/^#[0-9a-f]{3,8}$/i.test(e)?t.style.background=e:t.style.background="transparent",t}async function I(e){const t=e.querySelector(".ov-theme-diff");if(!t)return;for(;t.firstChild;)t.removeChild(t.firstChild);const n=e.querySelector(".ov-theme")?.value;if(!n)return;const o=$();if(n===o){const l=document.createElement("p");l.className="form-hint text-muted",l.style.cssText="margin:.4rem 0 0;font-size:.8rem;",l.textContent="That is already the site theme, so this changes nothing on its own. Set a token below instead.",t.appendChild(l);return}const[a,s]=await Promise.all([K(o),K(n)]);if(!a||!s)return;const c=Object.keys(s).filter(l=>String(a[l]??"").toLowerCase()!==String(s[l]).toLowerCase()),i=V.filter(l=>c.includes(l)),m=document.createElement("div");m.style.cssText="margin-top:.5rem;padding:.5rem .6rem;border-radius:var(--dm-radius-md);background:var(--dm-surface-raised);font-size:.8rem;";const h=document.createElement("p");if(h.style.cssText="margin:0 0 .35rem;",h.textContent=`Changes ${c.length} of ${Object.keys(s).length} tokens here.`,m.appendChild(h),!i.length){const l=document.createElement("p");l.style.cssText="margin:0 0 .35rem;color:var(--dm-warning);",l.textContent="Both themes use the same surface colour, so this region keeps the fill it has now. Set --dm-card-bg (a card) or --dm-surface (most other regions) below to change it.",m.appendChild(l)}const u=document.createElement("div");u.style.cssText="display:flex;flex-wrap:wrap;gap:.15rem .75rem;";const v=[..._.filter(l=>c.includes(l)),...c.filter(l=>!_.includes(l))];for(const l of v.slice(0,6)){const p=document.createElement("span");p.style.cssText="white-space:nowrap;color:var(--dm-text-muted);",p.appendChild(H(a[l]||""));const C=document.createElement("span");C.textContent=`${l} `,p.appendChild(C),p.appendChild(H(s[l])),u.appendChild(p)}if(v.length>6){const l=document.createElement("span");l.style.cssText="color:var(--dm-text-muted);",l.textContent=`+${v.length-6} more`,u.appendChild(l)}m.appendChild(u),t.appendChild(m)}let b=Array.isArray(d.overrides)?d.overrides.map(G):[];const f=r.find("#theme-frontend"),B=r.find("#theme-admin");w(f.get(0)),w(B.get(0),{includeAdmin:!0}),f.val(d.baseTheme||d.theme||"charcoal-dark"),B.val(d.adminTheme||"charcoal-dark"),d.baseTheme&&(f.prop("disabled",!0),r.find("#theme-auto-enabled").prop("disabled",!0),r.find("#theme-auto-roller-hint").show());const S=r.find("#theme-day"),D=r.find("#theme-night");w(S.get(0)),w(D.get(0));const g=d.autoTheme||{};S.val(g.dayTheme||"charcoal-light"),D.val(g.nightTheme||"charcoal-dark"),r.find("#theme-day-start").val(g.dayStart||"07:00"),r.find("#theme-night-start").val(g.nightStart||"19:00");const U=r.find("#theme-auto-enabled"),W=r.find("#theme-auto-fields");d.baseTheme||(U.prop("checked",!!g.enabled),g.enabled&&(W.show(),f.prop("disabled",!0)),U.on("change",function(){W.toggle(this.checked),f.prop("disabled",this.checked)})),r.find("#theme-font-family").val(d.font?.family||"Roboto"),r.find("#theme-font-size").val(String(d.font?.size??16));function G(e){return{id:e.id||`ov-${Date.now()}-${Math.random().toString(36).slice(2,8)}`,label:e.label||"",enabled:e.enabled!==!1,scope:e.scope||"",theme:e.theme||"",tokens:{...e.tokens||{}},css:e.css||""}}const y=document.getElementById("theme-overrides"),ee=document.getElementById("theme-overrides-empty");function L(){for(;y.firstChild;)y.removeChild(y.firstChild);ee.style.display=b.length?"none":"",b.forEach((e,t)=>y.appendChild(te(e,t))),Domma.icons.scan("#theme-overrides")}function te(e,t){const n=document.createElement("div");n.className="card mb-3",n.dataset.i=String(t);const o=document.createElement("div");o.className="card-header";const a=document.createElement("h3");a.className="m-0",a.style.fontSize="1rem",a.textContent=e.label||e.scope||`Override ${t+1}`,o.appendChild(a);const s=document.createElement("div");s.style.cssText="display:flex;gap:.5rem;align-items:center;";const c=document.createElement("label");c.style.cssText="display:flex;align-items:center;gap:.35rem;font-size:.85rem;cursor:pointer;";const i=document.createElement("input");i.type="checkbox",i.className="form-check ov-enabled",i.checked=e.enabled,c.appendChild(i),c.appendChild(document.createTextNode("Enabled")),s.appendChild(c);const m=document.createElement("button");m.type="button",m.className="btn btn-ghost btn-sm ov-remove",m.textContent="Remove",s.appendChild(m),o.appendChild(s),n.appendChild(o);const h=document.createElement("div");h.className="card-body";const u=document.createElement("div");u.className="row mb-3",u.appendChild(P("col-4","Name",X("ov-label",e.label,"Navbar island"))),u.appendChild(P("col-8","Target selector",ne(e.scope),"The region to re-theme. Everything inside it follows.")),h.appendChild(u);const v=document.createElement("div");v.className="row mb-3";const l=document.createElement("select");l.className="form-select ov-theme",w(l,{includeDefault:!0,defaultLabel:"Keep the site theme"}),l.value=e.theme||"",v.appendChild(P("col-6","Adopt a theme here",l,"Copies every token of that theme onto this region.")),h.appendChild(v);const p=document.createElement("div");p.className="ov-theme-diff",h.appendChild(p);const C=document.createElement("div");C.className="ov-tokens mb-2",Object.entries(e.tokens||{}).forEach(([oe,ae])=>{C.appendChild(J(oe,ae))}),h.appendChild(Q("Token overrides",C));const q=document.createElement("button");q.type="button",q.className="btn btn-ghost btn-sm ov-add-token",q.textContent="+ Add token",h.appendChild(q);const x=document.createElement("details");x.style.marginTop="1rem";const j=document.createElement("summary");j.style.cssText="cursor:pointer;font-size:.85rem;",j.textContent="Custom CSS for this override",x.appendChild(j);const k=document.createElement("textarea");k.className="form-input ov-css",k.rows=4,k.placeholder="#site-navbar .brand { letter-spacing: .04em; }",k.value=e.css||"",k.style.marginTop=".5rem",x.appendChild(k);const O=document.createElement("p");return O.className="form-hint text-muted",O.style.cssText="font-size:.8rem;margin-top:.35rem;",O.textContent="Appended verbatim after the generated rule - you own these selectors. Prefer tokens: they survive a Domma upgrade, hand-written rules fight the cascade.",x.appendChild(O),h.appendChild(x),n.appendChild(h),setTimeout(()=>I(n),0),n}function J(e,t){const n=document.createElement("div");n.className="ov-token-row",n.style.cssText="display:flex;gap:.5rem;align-items:center;margin-bottom:.5rem;";const o=document.createElement("select");if(o.className="form-select ov-token-name",o.style.flex="1 1 auto",z.length){for(const i of z){const m=document.createElement("option");m.value=i,m.textContent=i,o.appendChild(m)}o.value=e||z[0]}else{const i=document.createElement("option");i.value=e||"",i.textContent=e||"(no tokens found)",o.appendChild(i)}const a=document.createElement("input");a.type="text",a.className="form-input ov-token-value",a.style.flex="0 0 150px",a.placeholder="#0096c7",a.value=t||"";const s=document.createElement("input");s.type="color",s.className="ov-token-swatch",s.style.cssText="flex:0 0 38px;height:34px;padding:0;border:1px solid var(--dm-border);border-radius:var(--dm-radius-md);background:none;cursor:pointer;",/^#[0-9a-f]{6}$/i.test(t||"")&&(s.value=t),s.addEventListener("input",()=>{a.value=s.value,a.dispatchEvent(new Event("input",{bubbles:!0}))});const c=document.createElement("button");return c.type="button",c.className="btn btn-ghost btn-sm ov-token-remove",c.textContent="\xD7",n.append(o,a,s,c),n}function P(e,t,n,o){const a=document.createElement("div");return a.className=e,a.appendChild(Q(t,n,o)),a}function Q(e,t,n){const o=document.createDocumentFragment(),a=document.createElement("label");if(a.className="form-label",a.textContent=e,o.appendChild(a),o.appendChild(t),n){const s=document.createElement("p");s.className="form-hint text-muted",s.style.cssText="margin-top:.35rem;font-size:.8rem;",s.textContent=n,o.appendChild(s)}return o}function X(e,t,n){const o=document.createElement("input");return o.type="text",o.className=`form-input ${e}`,o.value=t||"",o.placeholder=n||"",o}function ne(e){const t=document.createElement("div"),n=X("ov-scope",e,"#site-navbar");if(n.setAttribute("list","theme-scope-options"),t.appendChild(n),!document.getElementById("theme-scope-options")){const o=document.createElement("datalist");o.id="theme-scope-options";for(const a of se){const s=document.createElement("option");s.value=a.value,s.label=a.label,o.appendChild(s)}t.appendChild(o)}return t}function N(){const e=y.querySelectorAll(".card"),t=[];return e.forEach((n,o)=>{const a={};n.querySelectorAll(".ov-token-row").forEach(s=>{const c=s.querySelector(".ov-token-name")?.value?.trim(),i=s.querySelector(".ov-token-value")?.value?.trim();c&&i&&(a[c]=i)}),t.push({id:b[o]?.id,label:n.querySelector(".ov-label")?.value?.trim()||"",enabled:!!n.querySelector(".ov-enabled")?.checked,scope:n.querySelector(".ov-scope")?.value?.trim()||"",theme:n.querySelector(".ov-theme")?.value||"",tokens:a,css:n.querySelector(".ov-css")?.value||""})}),b=t,t}r.on("click",".ov-remove",function(){const e=Number(this.closest(".card").dataset.i);N(),b.splice(e,1),L()}),r.on("change",".ov-theme",function(){I(this.closest(".card"))}),r.on("change","#theme-frontend, #theme-auto-enabled, #theme-day",()=>{y.querySelectorAll(".card").forEach(I)}),r.on("click",".ov-add-token",function(){this.closest(".card").querySelector(".ov-tokens").appendChild(J("",""))}),r.on("click",".ov-token-remove",function(){this.closest(".ov-token-row").remove()}),r.find("#theme-add-override").on("click",()=>{N(),b.push(G({label:"",scope:"",enabled:!0})),L()}),L();async function M(){const e=document.getElementById("theme-css-preview"),t={...Y(),overrides:N()};try{const n=await T.theme.compile(t);e.textContent=n.css?.trim()||"(no overrides - nothing is injected)",e.style.color=""}catch(n){e.textContent=n.message||String(n),e.style.color="var(--dm-danger)"}}r.find("#theme-refresh-css").on("click",M);function Y(){const e=!d.baseTheme&&r.find("#theme-auto-enabled").prop("checked"),t={enabled:e,dayTheme:S.val()||"charcoal-light",nightTheme:D.val()||"charcoal-dark",dayStart:r.find("#theme-day-start").val()||"07:00",nightStart:r.find("#theme-night-start").val()||"19:00"};return{theme:d.baseTheme?d.theme:e?t.dayTheme:f.val(),...d.baseTheme?{baseTheme:d.baseTheme}:{},adminTheme:B.val(),autoTheme:t,font:{family:r.find("#theme-font-family").val()||"Roboto",size:parseInt(r.find("#theme-font-size").val(),10)||16}}}function Z(e){const t=document.getElementById("theme-errors");for(;t.firstChild;)t.removeChild(t.firstChild);if(!e||!e.length){t.style.display="none";return}t.style.display="";for(const n of e){const o=document.createElement("div");o.textContent=n,t.appendChild(o)}t.scrollIntoView({behavior:"smooth",block:"nearest"})}r.find("#theme-save-btn").on("click",async function(){Z(null);const e={...Y(),overrides:N()};this.disabled=!0;try{d=await T.theme.save(e),Domma.theme.set(e.adminTheme),E.toast("Theme saved.",{type:"success"}),await M()}catch(t){const n=t.validation||[t.message||"Could not save the theme."];Z(n),E.toast("Theme not saved.",{type:"error"})}finally{this.disabled=!1}}),await M()}};
|
package/bin/cli.js
CHANGED
|
@@ -8,6 +8,7 @@ import {cpSync, existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync}
|
|
|
8
8
|
import {spawnSync} from 'node:child_process';
|
|
9
9
|
import path from 'node:path';
|
|
10
10
|
import {fileURLToPath} from 'node:url';
|
|
11
|
+
import {composeSmtp} from './lib/smtp-defaults.js';
|
|
11
12
|
|
|
12
13
|
// ---------------------------------------------------------------------------
|
|
13
14
|
// Paths
|
|
@@ -54,6 +55,7 @@ if (flags.has('--help') || args.includes('-h')) {
|
|
|
54
55
|
--no-backup Skip creating a backup before updating
|
|
55
56
|
--no-install Skip npm install after updating
|
|
56
57
|
--dry-run Preview what would change without writing anything
|
|
58
|
+
--force Re-install even when the installed version already matches
|
|
57
59
|
|
|
58
60
|
Examples:
|
|
59
61
|
npx domma-cms my-blog
|
|
@@ -61,6 +63,7 @@ if (flags.has('--help') || args.includes('-h')) {
|
|
|
61
63
|
npx domma-cms update
|
|
62
64
|
npx domma-cms update --dry-run
|
|
63
65
|
npx domma-cms update --yes --no-backup
|
|
66
|
+
npx domma-cms update --yes --force
|
|
64
67
|
`);
|
|
65
68
|
process.exit(0);
|
|
66
69
|
}
|
|
@@ -205,8 +208,13 @@ done();
|
|
|
205
208
|
// Reset config to clean defaults
|
|
206
209
|
// ---------------------------------------------------------------------------
|
|
207
210
|
|
|
211
|
+
// One placeholder name, used for the title and for the From name the SMTP
|
|
212
|
+
// block is seeded with. `npm run setup` renames both together, and only while
|
|
213
|
+
// they still agree - see scripts/setup.js.
|
|
214
|
+
const DEFAULT_SITE_TITLE = 'My Site';
|
|
215
|
+
|
|
208
216
|
const DEFAULT_SITE = {
|
|
209
|
-
title:
|
|
217
|
+
title: DEFAULT_SITE_TITLE,
|
|
210
218
|
tagline: 'Powered by Domma CMS',
|
|
211
219
|
theme: 'charcoal-dark',
|
|
212
220
|
seo: {
|
|
@@ -220,7 +228,8 @@ const DEFAULT_SITE = {
|
|
|
220
228
|
{text: 'Privacy Policy', url: '/privacy'},
|
|
221
229
|
{text: 'Contact', url: '/contact'}
|
|
222
230
|
]
|
|
223
|
-
}
|
|
231
|
+
},
|
|
232
|
+
smtp: composeSmtp(process.env, DEFAULT_SITE_TITLE)
|
|
224
233
|
};
|
|
225
234
|
|
|
226
235
|
const DEFAULT_NAV = {
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Domma CMS - SMTP defaults for a newly scaffolded site.
|
|
3
|
+
*
|
|
4
|
+
* Kept out of bin/cli.js because that file is a script: it parses argv and
|
|
5
|
+
* writes a project the moment it is loaded, so nothing in it can be imported
|
|
6
|
+
* by a test. This module is pure.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Compose the SMTP block seeded into a new site's config/site.json.
|
|
11
|
+
*
|
|
12
|
+
* A site.json with a missing or empty `smtp.host` does not fail - it silently
|
|
13
|
+
* routes every email through a throwaway Ethereal test account, which reports
|
|
14
|
+
* success and delivers to nobody. Password resets and form notifications then
|
|
15
|
+
* "work" right up until someone asks why they never arrived. So a new site
|
|
16
|
+
* always ships with a host, and with the whole block present so the fields are
|
|
17
|
+
* visible in Settings from the first boot.
|
|
18
|
+
*
|
|
19
|
+
* Precedence per field: environment → a local MTA on port 25. The fleet
|
|
20
|
+
* manager's own scaffolder (plugins/site-manager/services/siteScaffolder.js,
|
|
21
|
+
* `composeSmtp`) does the same thing one layer up, inheriting the manager's
|
|
22
|
+
* relay host/port; this is the equivalent for a site created straight from the
|
|
23
|
+
* CLI, where there is no manager to inherit from.
|
|
24
|
+
*
|
|
25
|
+
* Credentials are read from the environment rather than baked in, so nothing
|
|
26
|
+
* secret lives in the scaffolder. They are written into config/site.json,
|
|
27
|
+
* which is where this CMS keeps them - that file is yours and is never merged
|
|
28
|
+
* by the updater, but it is not a secret store either, so treat a site
|
|
29
|
+
* directory holding a relay password accordingly.
|
|
30
|
+
*
|
|
31
|
+
* @param {Record<string, string|undefined>} [env] Defaults to process.env
|
|
32
|
+
* @param {string} [siteName] Display name, used as the From name
|
|
33
|
+
* @returns {{host: string, port: number, secure: boolean, user: string, pass: string, fromAddress: string, fromName: string}}
|
|
34
|
+
*/
|
|
35
|
+
export function composeSmtp(env = process.env, siteName = 'My Site') {
|
|
36
|
+
const host = env.SMTP_HOST || '';
|
|
37
|
+
const port = Number.parseInt(env.SMTP_PORT, 10);
|
|
38
|
+
return {
|
|
39
|
+
// No host anywhere means a local MTA, which is the one thing that
|
|
40
|
+
// might plausibly be listening on a fresh box. It is still a guess,
|
|
41
|
+
// which is why the admin warns until someone confirms it.
|
|
42
|
+
host: host || 'localhost',
|
|
43
|
+
// 587 is the submission port a real relay listens on; 25 is what a
|
|
44
|
+
// local MTA takes. Defaulting to 25 for an env-supplied host would
|
|
45
|
+
// point a correctly configured relay at the wrong port.
|
|
46
|
+
port: Number.isFinite(port) && port > 0 ? port : (host ? 587 : 25),
|
|
47
|
+
secure: /^(1|true|yes|on)$/i.test(env.SMTP_SECURE || ''),
|
|
48
|
+
user: env.SMTP_USER || '',
|
|
49
|
+
pass: env.SMTP_PASS || '',
|
|
50
|
+
fromAddress: env.SMTP_FROM || '',
|
|
51
|
+
fromName: siteName
|
|
52
|
+
};
|
|
53
|
+
}
|