domma-cms 0.55.1 → 0.69.3
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 +194 -3
- package/README.md +18 -14
- package/admin/css/admin.css +1 -1
- package/admin/css/contacts.css +1 -0
- package/admin/dist/domma/domma-tools.css +3 -3
- package/admin/dist/domma/domma-tools.min.js +3 -3
- package/admin/index.html +1 -0
- package/admin/js/api.js +1 -1
- package/admin/js/app.js +4 -4
- package/admin/js/lib/analytics-shape.js +1 -0
- package/admin/js/lib/contacts-arrange.js +1 -0
- package/admin/js/lib/help-popover.js +1 -0
- package/admin/js/lib/notes-arrange.js +1 -0
- 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/lib/todo-arrange.js +1 -0
- package/admin/js/lib/tool-kit.js +1 -0
- package/admin/js/templates/analytics.html +138 -0
- package/admin/js/templates/contacts.html +156 -0
- package/admin/js/templates/context-menu-editor.html +212 -0
- package/admin/js/templates/context-menus.html +16 -0
- package/admin/js/templates/notes.html +137 -0
- package/admin/js/templates/plugin-code.html +1 -1
- package/admin/js/templates/plugin-marketplace.html +17 -4
- package/admin/js/templates/plugins.html +13 -7
- package/{plugins/site-search/admin/templates/site-search.html → admin/js/templates/search.html} +50 -4
- package/admin/js/templates/settings.html +16 -97
- package/admin/js/templates/theme.html +211 -0
- package/admin/js/templates/todo.html +113 -0
- package/admin/js/views/analytics.js +62 -0
- package/admin/js/views/contacts.js +174 -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/notes.js +104 -0
- package/admin/js/views/plugin-marketplace.js +1 -1
- package/admin/js/views/plugins.js +28 -24
- 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/admin/js/views/todo.js +96 -0
- package/bin/cli.js +6 -10
- package/bin/lib/plugin-version.js +28 -0
- package/bin/update.js +27 -2
- package/config/menus/admin-sidebar.json +129 -23
- package/config/plugins.json +5 -5
- package/config/search.json +13 -0
- package/config/server.json +3 -1
- package/package.json +14 -10
- 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 +4473 -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 +1022 -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/blog/CLAUDE.md +237 -0
- package/plugins/blog/admin/css/index.css +178 -0
- package/plugins/blog/admin/templates/blog.html +35 -41
- package/plugins/blog/admin/templates/categories.html +19 -6
- package/plugins/blog/admin/templates/comments.html +26 -9
- package/plugins/blog/admin/templates/post-editor.html +63 -40
- package/plugins/blog/admin/templates/settings.html +3 -9
- package/plugins/blog/admin/views/blog.js +288 -159
- package/plugins/blog/admin/views/categories.js +212 -206
- package/plugins/blog/admin/views/comments.js +207 -158
- package/plugins/blog/admin/views/kit.js +372 -0
- package/plugins/blog/admin/views/post-editor.js +306 -39
- package/plugins/blog/admin/views/settings-cog.js +56 -0
- package/plugins/blog/admin/views/settings.js +16 -84
- package/plugins/blog/blocks/blog-card-compact.css +17 -0
- package/plugins/blog/blocks/blog-card-compact.html +8 -0
- package/plugins/blog/blocks/blog-card-row.css +22 -0
- package/plugins/blog/blocks/blog-card-row.html +11 -0
- package/plugins/blog/blocks/blog-card.css +35 -0
- package/plugins/blog/blocks/blog-card.html +16 -0
- package/plugins/blog/blocks/blog-post-classic.css +15 -0
- package/plugins/blog/blocks/blog-post-classic.html +28 -0
- package/plugins/blog/blocks/blog-post-feature.css +20 -0
- package/plugins/blog/blocks/blog-post-feature.html +30 -0
- package/plugins/blog/blocks/blog-post-minimal.css +10 -0
- package/plugins/blog/blocks/blog-post-minimal.html +23 -0
- package/plugins/blog/blocks/blog-post-sidebar.css +22 -0
- package/plugins/blog/blocks/blog-post-sidebar.html +36 -0
- 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 +16 -13
- package/plugins/blog/config.js +6 -0
- package/plugins/blog/lib/layouts.js +304 -0
- package/plugins/blog/lib/page.js +158 -0
- package/plugins/blog/lib/render.js +119 -0
- package/plugins/blog/lib/samples.js +234 -0
- package/plugins/blog/plugin.js +259 -26
- package/plugins/blog/plugin.json +21 -73
- package/plugins/blog/plugin.public.js +217 -0
- package/plugins/blog/public/blog.css +177 -0
- package/plugins/blog/public/blog.js +586 -0
- package/plugins/blog/public/samples/aurora.svg +7 -0
- package/plugins/blog/public/samples/dusk.svg +7 -0
- package/plugins/blog/public/samples/ember.svg +7 -0
- package/plugins/blog/public/samples/harbour.svg +7 -0
- package/plugins/blog/public/samples/meadow.svg +7 -0
- package/plugins/blog/public/samples/rosewood.svg +7 -0
- package/plugins/blog/templates/index.html +9 -8
- package/plugins/blog/templates/post.html +4 -17
- package/plugins/blog/tests/layouts.test.js +97 -0
- package/plugins/blog/tests/public.test.js +71 -0
- package/plugins/free-tier.lock.json +74 -0
- package/plugins/mail-reader/CLAUDE.md +78 -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 +34 -0
- package/public/css/forms.css +1 -1
- package/public/css/search.css +1 -0
- package/public/css/site.css +1 -1
- package/public/css/theme-switcher.css +1 -0
- package/public/js/analytics.js +1 -0
- 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/search.js +1 -0
- package/public/js/site.js +1 -1
- package/public/js/theme-switcher.js +1 -0
- package/scripts/build.js +77 -4
- package/scripts/copy-domma.js +48 -0
- package/scripts/run-tests.mjs +92 -0
- package/scripts/seed.js +1996 -0
- package/{plugins/analytics/plugin.js → server/routes/api/analytics.js} +89 -41
- package/server/routes/api/collections.js +34 -0
- package/server/routes/api/contacts.js +506 -0
- package/server/routes/api/context-menus.js +104 -0
- package/server/routes/api/dashboard.js +8 -2
- package/server/routes/api/forms.js +42 -3
- package/{plugins/notes/plugin.js → server/routes/api/notes.js} +61 -17
- package/server/routes/api/notifications.js +69 -19
- package/server/routes/api/plugin-marketplace.js +78 -23
- package/server/routes/api/plugins.js +198 -9
- package/server/routes/api/search.js +43 -0
- package/server/routes/api/theme.js +69 -0
- package/server/routes/api/todo.js +178 -0
- package/server/routes/public.js +42 -7
- package/server/server.js +135 -1
- package/server/services/adapters/FileAdapter.js +6 -1
- package/server/services/collections.js +13 -3
- package/server/services/content.js +26 -0
- package/server/services/contextMenus.js +477 -0
- package/server/services/managerClient.js +72 -136
- package/server/services/markdown.js +124 -10
- package/server/services/permissionRegistry.js +74 -0
- package/server/services/pluginEntitlement.js +171 -0
- package/server/services/pluginEnvelope.js +242 -0
- package/server/services/pluginFiles.js +52 -11
- package/server/services/pluginInstaller.js +246 -26
- package/server/services/pluginScaffold.js +5 -0
- package/server/services/plugins.js +340 -7
- package/server/services/presetCollections.js +344 -0
- package/server/services/promoted-plugins-migration.js +254 -0
- package/server/services/publicCors.js +46 -0
- package/server/services/renderer.js +224 -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/sidebarBadges.js +37 -0
- package/server/services/siteGitignore.js +307 -0
- package/server/services/themeSettings.js +580 -0
- package/server/services/users.js +8 -0
- package/server/templates/page.html +4 -2
- package/plugins/analytics/admin/templates/analytics.html +0 -61
- package/plugins/analytics/admin/views/analytics.js +0 -176
- package/plugins/analytics/config.js +0 -14
- package/plugins/analytics/plugin.json +0 -57
- package/plugins/analytics/public/inject-body.html +0 -60
- package/plugins/analytics/public/inject-head.html +0 -1
- package/plugins/blog/templates/author.html +0 -9
- package/plugins/blog/templates/category.html +0 -9
- package/plugins/blog/templates/tag.html +0 -9
- package/plugins/contacts/admin/templates/contacts.html +0 -126
- package/plugins/contacts/admin/views/contacts.js +0 -710
- package/plugins/contacts/collections/user-contact-groups/schema.json +0 -35
- package/plugins/contacts/collections/user-contacts/schema.json +0 -71
- package/plugins/contacts/config.js +0 -6
- package/plugins/contacts/data/contacts.json +0 -20
- package/plugins/contacts/plugin.js +0 -291
- package/plugins/contacts/plugin.json +0 -33
- package/plugins/demo-viewer/config.js +0 -4
- package/plugins/demo-viewer/plugin.js +0 -36
- package/plugins/demo-viewer/plugin.json +0 -9
- package/plugins/invoice/admin/templates/editor.html +0 -129
- package/plugins/invoice/admin/templates/index.html +0 -43
- package/plugins/invoice/admin/templates/issuers.html +0 -5
- package/plugins/invoice/admin/templates/receivers.html +0 -5
- package/plugins/invoice/admin/views/editor.js +0 -267
- package/plugins/invoice/admin/views/index.js +0 -155
- package/plugins/invoice/admin/views/issuers.js +0 -23
- package/plugins/invoice/admin/views/party-view.js +0 -148
- package/plugins/invoice/admin/views/receivers.js +0 -22
- package/plugins/invoice/collections/invoice-issuers/schema.json +0 -16
- package/plugins/invoice/collections/invoice-receivers/schema.json +0 -15
- package/plugins/invoice/collections/invoices/schema.json +0 -27
- package/plugins/invoice/config.js +0 -16
- package/plugins/invoice/plugin.js +0 -283
- package/plugins/invoice/plugin.json +0 -85
- package/plugins/invoice/templates/invoice-print.html +0 -213
- package/plugins/notes/admin/templates/notes.html +0 -83
- package/plugins/notes/admin/views/notes.js +0 -281
- package/plugins/notes/collections/user-notes/schema.json +0 -54
- package/plugins/notes/config.js +0 -6
- package/plugins/notes/data/notes.json +0 -1
- package/plugins/notes/plugin.json +0 -33
- 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/surveys/admin/templates/audience.html +0 -47
- package/plugins/surveys/admin/templates/results.html +0 -58
- package/plugins/surveys/admin/templates/survey-editor.html +0 -141
- package/plugins/surveys/admin/templates/surveys.html +0 -25
- package/plugins/surveys/admin/views/audience.js +0 -301
- package/plugins/surveys/admin/views/results.js +0 -172
- package/plugins/surveys/admin/views/survey-editor.js +0 -211
- package/plugins/surveys/admin/views/surveys.js +0 -161
- package/plugins/surveys/collections/survey-contacts/schema.json +0 -13
- package/plugins/surveys/collections/survey-groups/schema.json +0 -11
- package/plugins/surveys/collections/survey-invites/schema.json +0 -16
- package/plugins/surveys/collections/surveys/schema.json +0 -23
- package/plugins/surveys/config.js +0 -8
- package/plugins/surveys/plugin.js +0 -174
- package/plugins/surveys/plugin.json +0 -65
- package/plugins/surveys/public/dist-shared.mjs +0 -1
- package/plugins/surveys/public/survey.css +0 -1
- package/plugins/surveys/public/survey.mjs +0 -1
- package/plugins/surveys/templates/survey-page.html +0 -63
- package/plugins/theme-switcher/admin/templates/theme-switcher.html +0 -86
- package/plugins/theme-switcher/admin/views/theme-switcher.js +0 -65
- package/plugins/theme-switcher/config.js +0 -10
- package/plugins/theme-switcher/plugin.js +0 -26
- package/plugins/theme-switcher/plugin.json +0 -74
- package/plugins/theme-switcher/public/inject-body.html +0 -153
- package/plugins/theme-switcher/public/inject-head.html +0 -260
- package/plugins/todo/admin/templates/todo.html +0 -158
- package/plugins/todo/admin/views/todo.js +0 -343
- package/plugins/todo/collections/todos/schema.json +0 -60
- package/plugins/todo/config.js +0 -7
- package/plugins/todo/data/todos.json +0 -1
- package/plugins/todo/plugin.js +0 -102
- package/plugins/todo/plugin.json +0 -33
|
@@ -1,39 +1,43 @@
|
|
|
1
|
-
import{api as
|
|
2
|
-
<div class="card plugin-card" data-plugin="${e.name}">
|
|
1
|
+
import{api as f,getUser as C}from"../api.js";import{pluginMarketplaceView as x}from"./plugin-marketplace.js";import{pluginAccent as S,pluginWatermark as T}from"../lib/plugin-accent.js";import{reportRestart as N}from"../lib/server-restart.js";export const pluginsView={templateUrl:"/admin/js/templates/plugins.html",async onMount(s){s.find("#new-plugin-btn").on("click",()=>I(s));const c=s.find("#install-file-input").get(0);s.find("#install-file-btn").on("click",()=>c?.click()),c?.addEventListener("change",async()=>{const i=c.files?.[0];c.value="",i&&await L(s,i)});let o=!1;s.find('[data-tab="marketplace"]').on("click",async function(){if(o)return;const i=s.find("#marketplace-mount");try{const n=await fetch(x.templateUrl);if(!n.ok)throw new Error(`Template ${n.status}`);const d=await n.text(),p=new DOMParser().parseFromString(d,"text/html");for(;p.body.firstChild;)i.get(0).appendChild(p.body.firstChild);await x.onMount(i),o=!0}catch{E.toast("Failed to load marketplace.",{type:"error"})}}),E.tabs(s.find("#plugin-tabs").get(0));const e=E.loader(s.get(0),{type:"dots"});let t=[];try{t=await f.plugins.list()}catch(i){E.toast(`Failed to load plugins: ${i.message}`,{type:"error"})}if(e.destroy(),!t.length){s.find("#plugins-empty").show();return}y(s,t),Domma.icons.scan()}};async function L(s,c){const o=s.find("#install-file-btn"),e=()=>o.prop("disabled",!1).html('<span data-icon="upload"></span> Install from File');o.prop("disabled",!0).text("Installing\u2026");const t={};for(let i=0;i<4;i++)try{const n=await f.plugins.installFile(c,t),d=n.action==="upgrade"?`${n.name} updated${n.from?` from ${n.from}`:""} to ${n.version}.`:`${n.name} ${n.version} installed.`;E.toast(`${d} Restart the server to activate it.`,{type:"success"}),e();const l=await f.plugins.list();s.find("#plugins-empty").hide(),y(s,l);return}catch(n){if(n.code==="CONFLICT"&&!t.confirmUpgrade){const d=n.installedVersion||"the installed version",l=n.incomingVersion||"this file";if(!await E.confirm(`<p>${r(n.name)} ${r(d)} is already installed. Update it to ${r(l)}?</p><p class="text-muted">The plugin keeps its data, and the current version is put back if the update fails.</p>`,{title:"Update plugin",confirmText:"Update",cancelText:"Cancel",html:!0}))break;t.confirmUpgrade=!0;continue}if(/refusing to downgrade/i.test(n.message)&&!t.confirmDowngrade){if(!await E.confirm(`<p>${r(n.message)} Install it anyway?</p><p class="text-muted">An older version may not understand data written by the newer one.</p>`,{title:"Downgrade plugin",confirmText:"Downgrade",cancelText:"Cancel",html:!0}))break;t.confirmDowngrade=!0;continue}if(/unsigned|signature/i.test(n.message)&&!t.confirmUnsigned){if(!await E.confirm(`<p>${r(n.message)}</p><p class="text-muted">An unsigned plugin has not been verified as coming from the marketplace. Its code runs on this server with full access. Install it anyway?</p>`,{title:"Unsigned plugin",confirmText:"Install anyway",cancelText:"Cancel",html:!0}))break;t.confirmUnsigned=!0;continue}E.toast(n.message||"Install failed.",{type:"error"});break}e(),Domma.icons.scan()}function I(s){const c=E.slideover({title:"New Plugin",size:"sm",position:"right"}),o=document.createElement("div");o.style.cssText="padding:.25rem 0 .5rem;";const e=C(),t=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(t),o.appendChild(t);const i=document.createElement("p");i.className="text-muted",i.style.cssText="font-size:.8rem;margin:.5rem 0 0;",i.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.",o.appendChild(i);const n=document.createElement("div");n.style.cssText="display:flex;justify-content:flex-end;gap:1rem;margin-top:.75rem;";const d=document.createElement("button");d.className="btn btn-ghost",d.textContent="Cancel";const l=document.createElement("button");l.className="btn btn-primary",l.textContent="Create Plugin",n.appendChild(d),n.appendChild(l),o.appendChild(n),c.element.appendChild(o),c.open();const p=t.querySelector('input[name="displayName"]'),a=t.querySelector('input[name="slug"]');setTimeout(()=>p?.focus(),50);let u=!1;a?.addEventListener("input",()=>{u=!0}),p?.addEventListener("input",()=>{u||!a||(a.value=p.value.toLowerCase().trim().replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,"").slice(0,40))});async function b(){const m={};for(const g of["displayName","slug","description","author","icon"])m[g]=t.querySelector(`input[name="${g}"]`)?.value.trim()||"";if(!m.displayName||!m.slug){E.toast("Display name and slug are required.",{type:"error"});return}l.disabled=!0,l.textContent="Creating\u2026";try{await f.plugins.scaffold(m),c.close(),E.toast(`Plugin "${m.displayName}" created. Restart the server to activate it.`,{type:"success"});const g=await f.plugins.list();g.length&&(s.find("#plugins-empty").hide(),y(s,g))}catch(g){E.toast(g.message||"Failed to create plugin.",{type:"error"}),l.disabled=!1,l.textContent="Create Plugin"}}d.addEventListener("click",()=>c.close()),l.addEventListener("click",b),a?.addEventListener("keydown",m=>{m.key==="Enter"&&b()})}function U(s){if(!s)return"";const c=new Date(s);if(Number.isNaN(c.getTime()))return"";try{return D(s).format("DD.MM.YYYY")}catch{return""}}function B(s){const o={grace:["Renew","var(--dm-warning,#d97706)","The rental term has ended - still running during the grace period"],"support-ended":["Support ended","var(--dm-info,#0284c7)","Owned outright and still running, but no longer entitled to updates"],expired:["Expired","var(--dm-danger,#dc2626)","The rental term and grace period have ended - this plugin is not running"],unlicensed:["Unlicensed","var(--dm-danger,#dc2626)","No valid entitlement for this site - this plugin is not running"]}[s];if(!o)return"";const[e,t,i]=o;return`<span class="badge badge-outline" title="${r(i)}" style="font-size:0.65rem;padding:1px 6px;background:none;border:1px solid;color:${t};border-color:${t};">${e}</span>`}function y(s,c){const o=s.find("#plugins-grid").empty();c.forEach(e=>{const t=!!e.core,i=e.sourceEditable!==!1,n="var(--dm-warning,#d97706)",d=e.closedSource?`<span class="badge badge-outline" title="${r(e.licence||"Commercial licence")} - source is not editable here" style="font-size:0.65rem;padding:1px 6px;background:none;border:1px solid;color:${n};border-color:${n};">Licensed</span>`:"",l=B(e.entitlement),p=e.enabled&&["expired","unlicensed"].includes(e.entitlement),a=e.enabled&&e.supersededBy,u=t?"badge-info":p?"badge-danger":a?"badge-secondary":e.enabled?"badge-success":"badge-secondary",b=t?"Built in":p?"Not running":a?"Replaced":e.enabled?"Enabled":"Disabled",m=e.enabled?"Disable":"Enable",g=e.enabled?"btn-ghost":"btn-primary",h=e.author?`by ${r(e.author)}`:"",v=U(e.date),w=e.icon||"package",k=`
|
|
2
|
+
<div class="card plugin-card" data-plugin="${e.name}" style="--plugin-accent:${S(e.name)}">
|
|
3
|
+
${T(w).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="${w}"></span></div>
|
|
6
7
|
<div class="plugin-meta">
|
|
7
|
-
<div class="plugin-name">${
|
|
8
|
-
<div class="plugin-version">v${
|
|
8
|
+
<div class="plugin-name">${r(e.displayName)}</div>
|
|
9
|
+
<div class="plugin-version">v${r(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
|
+
${d}
|
|
13
|
+
${l}
|
|
14
|
+
<span class="badge ${u}">${b}</span>
|
|
13
15
|
</div>
|
|
14
16
|
</div>
|
|
15
|
-
<p class="plugin-desc">${
|
|
16
|
-
${
|
|
17
|
+
<p class="plugin-desc">${r(e.description)}</p>
|
|
18
|
+
${v?`<p class="plugin-date text-muted" style="font-size:.8rem;margin:0">Released ${v}</p>`:""}
|
|
19
|
+
${a?`<p class="text-muted" style="font-size:.8rem;margin:.25rem 0 0">Replaced by ${r(e.supersededBy)}.</p>`:""}
|
|
17
20
|
</div>
|
|
18
21
|
<div class="plugin-footer">
|
|
19
|
-
<span class="text-muted" style="font-size:.8rem"
|
|
22
|
+
<span class="text-muted" style="font-size:.8rem">${t?"Part of the CMS - always on":"Restart server after changes"}</span>
|
|
20
23
|
<div class="plugin-footer-actions">
|
|
21
|
-
|
|
24
|
+
${i?`<button class="btn btn-sm btn-ghost btn-plugin-code" data-name="${e.name}"><span data-icon="code"></span> Code</button>`:""}
|
|
25
|
+
${e.closedSource?"":`<button class="btn btn-sm btn-ghost btn-plugin-export" data-name="${e.name}" data-tooltip="Export as .dcmsplugin"><span data-icon="download" data-icon-size="14"></span></button>`}
|
|
22
26
|
${e.settingsSchema?`<button class="btn btn-sm btn-ghost btn-plugin-settings" data-name="${e.name}"><span data-icon="settings"></span> Settings</button>`:""}
|
|
23
|
-
|
|
27
|
+
${t?"":`<button class="btn btn-sm ${g} btn-toggle-plugin"
|
|
24
28
|
data-name="${e.name}"
|
|
25
29
|
data-enabled="${e.enabled?"1":"0"}">
|
|
26
|
-
<span data-icon="${e.enabled?"pause":"play"}"></span> ${
|
|
27
|
-
</button
|
|
30
|
+
<span data-icon="${e.enabled?"pause":"play"}"></span> ${m}
|
|
31
|
+
</button>`}
|
|
28
32
|
</div>
|
|
29
33
|
</div>
|
|
30
|
-
</div>`;
|
|
31
|
-
<label class="form-label">${
|
|
32
|
-
<select class="form-control" name="${
|
|
33
|
-
</div>`}return
|
|
34
|
-
<label class="form-label">${
|
|
35
|
-
<input class="form-control" type="number" name="${
|
|
34
|
+
</div>`;o.append(k)}),Domma.icons.scan(),Domma.effects.reveal(".plugin-card",{animation:"fade",stagger:60,duration:400}),Domma.effects.ripple(".btn-toggle-plugin"),o.on("click",".btn-plugin-code",function(){R.navigate(`/plugins/code/${$(this).data("name")}`)}),o.on("click",".btn-plugin-export",async function(){const e=$(this).data("name");try{await f.plugins.exportBundle(e)}catch(t){E.toast(t.message||"Export failed.",{type:"error"})}}),o.on("click",".btn-plugin-settings",async function(){const e=$(this).data("name"),t=c.find(a=>a.name===e);if(!t?.settingsSchema)return;const n=`<form id="plugin-settings-form">${t.settingsSchema.map(a=>{const u=t.settings?.[a.key]??a.default??"";if(a.type==="select"){const b=a.options.map(m=>`<option value="${r(m.value)}"${String(u)===String(m.value)?" selected":""}>${r(m.label)}</option>`).join("");return`<div class="form-group">
|
|
35
|
+
<label class="form-label">${r(a.label)}</label>
|
|
36
|
+
<select class="form-control" name="${r(a.key)}">${b}</select>
|
|
37
|
+
</div>`}return a.type==="number"?`<div class="form-group">
|
|
38
|
+
<label class="form-label">${r(a.label)}</label>
|
|
39
|
+
<input class="form-control" type="number" name="${r(a.key)}" value="${r(String(u))}"${a.min!=null?` min="${a.min}"`:""}${a.max!=null?` max="${a.max}"`:""}>
|
|
36
40
|
</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(
|
|
41
|
+
<label class="form-label">${r(a.label)}</label>
|
|
42
|
+
<input class="form-control" type="text" name="${r(a.key)}" value="${r(String(u))}">
|
|
43
|
+
</div>`}).join("")}</form>`;if(!await E.confirm(n,{title:`${r(t.displayName)} Settings`,confirmText:"Save",cancelText:"Cancel",html:!0}))return;const l=document.getElementById("plugin-settings-form");if(!l)return;const p={};t.settingsSchema.forEach(a=>{const u=l.elements[a.key];u&&(p[a.key]=a.type==="number"?Number(u.value):u.value)});try{await f.plugins.update(e,{settings:p}),t.settings={...t.settings,...p},E.toast("Settings saved.",{type:"success"})}catch(a){E.toast(`Failed to save: ${a.message}`,{type:"error"})}}),o.on("click",".btn-toggle-plugin",async function(){const e=$(this),t=e.data("name"),i=e.data("enabled")!==1&&e.data("enabled")!=="1";e.prop("disabled",!0).text(i?"Enabling\u2026":"Disabling\u2026");try{const n=await f.plugins.update(t,{enabled:i});E.toast(`Plugin ${i?"enabled":"disabled"}.`,{type:"success"}),await N(n,{onStatus:l=>e.text(l.slice(0,24))});const d=await f.plugins.list();y(s,d)}catch(n){E.toast(`Failed: ${n.message}`,{type:"error"}),e.prop("disabled",!1)}})}function r(s){return String(s).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 x}from"../api.js";import{populateThemeSelect as T}from"../lib/themes.js";const ce=[{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(o){E.tabs(o.find("#theme-tabs").get(0));const K=E.loader(o.get(0),{type:"dots"});let d,V;try{[d,V]=await Promise.all([x.theme.get(),x.theme.catalog().catch(()=>({themes:[],tokens:[]}))])}catch(e){K.destroy(),E.toast(`Could not load theme settings: ${e.message||e}`,{type:"error"});return}K.destroy();const z=V.tokens||[],B=new Map;async function _(e){if(!e)return null;if(B.has(e))return B.get(e);const t=await x.theme.themeTokens(e).catch(()=>null),n=t&&t.tokens?t.tokens:null;return B.set(e,n),n}function ne(){const e=o.find("#theme-auto-enabled").prop("checked");return d.baseTheme||(e?S.val():f.val())||d.theme}const X=["--dm-card-bg","--dm-surface"],Y=[...X,"--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 D(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 s=ne();if(n===s){const r=document.createElement("p");r.className="form-hint text-muted",r.style.cssText="margin:.4rem 0 0;font-size:.8rem;",r.textContent="That is already the site theme, so this changes nothing on its own. Set a token below instead.",t.appendChild(r);return}const[a,l]=await Promise.all([_(s),_(n)]);if(!a||!l)return;const c=Object.keys(l).filter(r=>String(a[r]??"").toLowerCase()!==String(l[r]).toLowerCase()),i=X.filter(r=>c.includes(r)),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(l).length} tokens here.`,m.appendChild(h),!i.length){const r=document.createElement("p");r.style.cssText="margin:0 0 .35rem;color:var(--dm-warning);",r.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(r)}const p=document.createElement("div");p.style.cssText="display:flex;flex-wrap:wrap;gap:.15rem .75rem;";const v=[...Y.filter(r=>c.includes(r)),...c.filter(r=>!Y.includes(r))];for(const r of v.slice(0,6)){const u=document.createElement("span");u.style.cssText="white-space:nowrap;color:var(--dm-text-muted);",u.appendChild(H(a[r]||""));const C=document.createElement("span");C.textContent=`${r} `,u.appendChild(C),u.appendChild(H(l[r])),p.appendChild(u)}if(v.length>6){const r=document.createElement("span");r.style.cssText="color:var(--dm-text-muted);",r.textContent=`+${v.length-6} more`,p.appendChild(r)}m.appendChild(p),t.appendChild(m)}let b=Array.isArray(d.overrides)?d.overrides.map(J):[];const f=o.find("#theme-frontend"),F=o.find("#theme-admin");T(f.get(0)),T(F.get(0),{includeAdmin:!0}),f.val(d.baseTheme||d.theme||"charcoal-dark"),F.val(d.adminTheme||"charcoal-dark"),d.baseTheme&&(f.prop("disabled",!0),o.find("#theme-auto-enabled").prop("disabled",!0),o.find("#theme-auto-roller-hint").show());const S=o.find("#theme-day"),L=o.find("#theme-night");T(S.get(0)),T(L.get(0));const y=d.autoTheme||{};S.val(y.dayTheme||"charcoal-light"),L.val(y.nightTheme||"charcoal-dark"),o.find("#theme-day-start").val(y.dayStart||"07:00"),o.find("#theme-night-start").val(y.nightStart||"19:00");const N=d.switcher||{},q=o.find("#theme-switcher-enabled"),oe=o.find("#theme-switcher-fields");q.prop("checked",N.enabled===!0),o.find("#theme-switcher-position").val(N.position||"right-center"),o.find("#theme-switcher-offset-x").val(N.offsetX??32),o.find("#theme-switcher-offset-y").val(N.offsetY??32);const U=()=>{oe.get(0).style.display=q.prop("checked")?"":"none"};U(),q.on("change",U);const W=o.find("#theme-auto-enabled"),G=o.find("#theme-auto-fields");d.baseTheme||(W.prop("checked",!!y.enabled),y.enabled&&(G.show(),f.prop("disabled",!0)),W.on("change",function(){G.toggle(this.checked),f.prop("disabled",this.checked)})),o.find("#theme-font-family").val(d.font?.family||"Roboto"),o.find("#theme-font-size").val(String(d.font?.size??16));function J(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 g=document.getElementById("theme-overrides"),se=document.getElementById("theme-overrides-empty");function P(){for(;g.firstChild;)g.removeChild(g.firstChild);se.style.display=b.length?"none":"",b.forEach((e,t)=>g.appendChild(ae(e,t))),Domma.icons.scan("#theme-overrides")}function ae(e,t){const n=document.createElement("div");n.className="card mb-3",n.dataset.i=String(t);const s=document.createElement("div");s.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}`,s.appendChild(a);const l=document.createElement("div");l.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")),l.appendChild(c);const m=document.createElement("button");m.type="button",m.className="btn btn-ghost btn-sm ov-remove",m.textContent="Remove",l.appendChild(m),s.appendChild(l),n.appendChild(s);const h=document.createElement("div");h.className="card-body";const p=document.createElement("div");p.className="row mb-3",p.appendChild(M("col-4","Name",$("ov-label",e.label,"Navbar island"))),p.appendChild(M("col-8","Target selector",le(e.scope),"The region to re-theme. Everything inside it follows.")),h.appendChild(p);const v=document.createElement("div");v.className="row mb-3";const r=document.createElement("select");r.className="form-select ov-theme",T(r,{includeDefault:!0,defaultLabel:"Keep the site theme"}),r.value=e.theme||"",v.appendChild(M("col-6","Adopt a theme here",r,"Copies every token of that theme onto this region.")),h.appendChild(v);const u=document.createElement("div");u.className="ov-theme-diff",h.appendChild(u);const C=document.createElement("div");C.className="ov-tokens mb-2",Object.entries(e.tokens||{}).forEach(([re,de])=>{C.appendChild(Q(re,de))}),h.appendChild(Z("Token overrides",C));const A=document.createElement("button");A.type="button",A.className="btn btn-ghost btn-sm ov-add-token",A.textContent="+ Add token",h.appendChild(A);const w=document.createElement("details");w.style.marginTop="1rem";const R=document.createElement("summary");R.style.cssText="cursor:pointer;font-size:.85rem;",R.textContent="Custom CSS for this override",w.appendChild(R);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",w.appendChild(k);const I=document.createElement("p");return I.className="form-hint text-muted",I.style.cssText="font-size:.8rem;margin-top:.35rem;",I.textContent="Appended verbatim after the generated rule - you own these selectors. Prefer tokens: they survive a Domma upgrade, hand-written rules fight the cascade.",w.appendChild(I),h.appendChild(w),n.appendChild(h),setTimeout(()=>D(n),0),n}function Q(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 s=document.createElement("select");if(s.className="form-select ov-token-name",s.style.flex="1 1 auto",z.length){for(const i of z){const m=document.createElement("option");m.value=i,m.textContent=i,s.appendChild(m)}s.value=e||z[0]}else{const i=document.createElement("option");i.value=e||"",i.textContent=e||"(no tokens found)",s.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 l=document.createElement("input");l.type="color",l.className="ov-token-swatch",l.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||"")&&(l.value=t),l.addEventListener("input",()=>{a.value=l.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(s,a,l,c),n}function M(e,t,n,s){const a=document.createElement("div");return a.className=e,a.appendChild(Z(t,n,s)),a}function Z(e,t,n){const s=document.createDocumentFragment(),a=document.createElement("label");if(a.className="form-label",a.textContent=e,s.appendChild(a),s.appendChild(t),n){const l=document.createElement("p");l.className="form-hint text-muted",l.style.cssText="margin-top:.35rem;font-size:.8rem;",l.textContent=n,s.appendChild(l)}return s}function $(e,t,n){const s=document.createElement("input");return s.type="text",s.className=`form-input ${e}`,s.value=t||"",s.placeholder=n||"",s}function le(e){const t=document.createElement("div"),n=$("ov-scope",e,"#site-navbar");if(n.setAttribute("list","theme-scope-options"),t.appendChild(n),!document.getElementById("theme-scope-options")){const s=document.createElement("datalist");s.id="theme-scope-options";for(const a of ce){const l=document.createElement("option");l.value=a.value,l.label=a.label,s.appendChild(l)}t.appendChild(s)}return t}function O(){const e=g.querySelectorAll(".card"),t=[];return e.forEach((n,s)=>{const a={};n.querySelectorAll(".ov-token-row").forEach(l=>{const c=l.querySelector(".ov-token-name")?.value?.trim(),i=l.querySelector(".ov-token-value")?.value?.trim();c&&i&&(a[c]=i)}),t.push({id:b[s]?.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}o.on("click",".ov-remove",function(){const e=Number(this.closest(".card").dataset.i);O(),b.splice(e,1),P()}),o.on("change",".ov-theme",function(){D(this.closest(".card"))}),o.on("change","#theme-frontend, #theme-auto-enabled, #theme-day",()=>{g.querySelectorAll(".card").forEach(D)}),o.on("click",".ov-add-token",function(){this.closest(".card").querySelector(".ov-tokens").appendChild(Q("",""))}),o.on("click",".ov-token-remove",function(){this.closest(".ov-token-row").remove()}),o.find("#theme-add-override").on("click",()=>{O(),b.push(J({label:"",scope:"",enabled:!0})),P()}),P();async function j(){const e=document.getElementById("theme-css-preview"),t={...ee(),overrides:O()};try{const n=await x.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)"}}o.find("#theme-refresh-css").on("click",j);function ee(){const e=!d.baseTheme&&o.find("#theme-auto-enabled").prop("checked"),t={enabled:e,dayTheme:S.val()||"charcoal-light",nightTheme:L.val()||"charcoal-dark",dayStart:o.find("#theme-day-start").val()||"07:00",nightStart:o.find("#theme-night-start").val()||"19:00"};return{theme:d.baseTheme?d.theme:e?t.dayTheme:f.val(),...d.baseTheme?{baseTheme:d.baseTheme}:{},adminTheme:F.val(),autoTheme:t,font:{family:o.find("#theme-font-family").val()||"Roboto",size:parseInt(o.find("#theme-font-size").val(),10)||16},switcher:{enabled:q.prop("checked"),position:o.find("#theme-switcher-position").val()||"right-center",offsetX:parseInt(o.find("#theme-switcher-offset-x").val(),10)||0,offsetY:parseInt(o.find("#theme-switcher-offset-y").val(),10)||0,themes:Array.isArray(d.switcher?.themes)?d.switcher.themes:[]}}}function te(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 s=document.createElement("div");s.textContent=n,t.appendChild(s)}t.scrollIntoView({behavior:"smooth",block:"nearest"})}o.find("#theme-save-btn").on("click",async function(){te(null);const e={...ee(),overrides:O()};this.disabled=!0;try{d=await x.theme.save(e),Domma.theme.set(e.adminTheme),E.toast("Theme saved.",{type:"success"}),await j()}catch(t){const n=t.validation||[t.message||"Could not save the theme."];te(n),E.toast("Theme not saved.",{type:"error"})}finally{this.disabled=!1}}),await j()}};
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import{apiRequest as at}from"../api.js";import{addBannerAction as st}from"../lib/plugin-chrome.js";import{helpPopovers as O}from"../lib/help-popover.js";import{onceClosed as j,openMenuAt as lt,settingsStore as nt,sidebarCount as it,viewLifetime as dt}from"../lib/tool-kit.js";import{arrangeTodos as rt,countTodos as ct,daysUntil as ut,DEFAULT_SETTINGS as H,dueDay as pt,dueLabel as bt,isoDay as k,isOverdue as mt,normaliseSettings as K,PRIORITIES as P,STATUSES as B}from"../lib/todo-arrange.js";const u={"not-started":"Not started","in-progress":"In progress",completed:"Completed",highest:"Highest",high:"High",medium:"Medium",low:"Low",lowest:"Lowest",open:"Open",all:"All",overdue:"Overdue"},U=nt("todo.settings",K),v=(b,d="GET",n)=>at(b,{method:d,...n!==void 0?{body:JSON.stringify(n)}:{}}),vt=`
|
|
2
|
+
<div class="todo-row{{doneClass}}" data-id="{{id}}">
|
|
3
|
+
<input type="checkbox" class="todo-check" aria-label="Done" data-bind-checked="isDone">
|
|
4
|
+
<span class="todo-text" title="Click to edit">{{text}}</span>
|
|
5
|
+
<span class="todo-due{{dueClass}}" title="{{dueTitle}}">{{due}}</span>
|
|
6
|
+
<button type="button" class="todo-pill" data-kind="status" data-value="{{status}}" title="Change status">{{statusLabel}}</button>
|
|
7
|
+
<button type="button" class="todo-pill" data-kind="priority" data-value="{{priority}}" title="Change priority">{{priorityLabel}}</button>
|
|
8
|
+
<button type="button" class="todo-kebab" aria-label="Task actions" title="Actions">\u22EE</button>
|
|
9
|
+
</div>`;function z(b){const d=document.createElement("template");return d.innerHTML=b.trim(),d.content.firstElementChild}const p=dt(/^#\/todo\b/);export const todoView={templateUrl:"/admin/js/templates/todo.html",async onMount(b){p.reset();const d=b.get(0),n=M.observable(U.read()),l=M.observable([]),m=M.observable(!0),g=M.observable(""),w=M.observable(n.peek().defaultFilter),f=M.observable(""),T=M.observable(n.peek().defaultPriority),x=M.observable(""),A=M.observable(!1),N=M.observable(!1),S=M.computed(()=>ct(l.value)),G=M.computed(()=>["open","all","not-started","in-progress","completed","overdue"].map(e=>({value:e,label:`${u[e]} (${S.value[e]})`}))),y=M.observable([]);p.add(M.effect(()=>{const e=n.value.relativeDue;y.value=rt(l.value,{filter:w.value,query:g.value,sort:n.value.sort}).map(t=>{const o=ut(t.dueAt),s=mt(t);return{id:t.id,text:t.text,status:t.status,priority:t.priority,statusLabel:u[t.status]??t.status,priorityLabel:u[t.priority]??t.priority,doneClass:t.status==="completed"?" is-done":"",isDone:t.status==="completed",due:bt(t.dueAt,{relative:e,format:i=>D(i).format("D MMM")}),dueClass:s?" is-overdue":o===0&&t.status!=="completed"?" is-today":"",dueTitle:o===null?"":`Due ${D(pt(t.dueAt)).format("dddd D MMMM YYYY")}`}})}));const W={rows:y,loading:m,query:g,filter:w,filterOptions:G,counts:S,draft:f,draftPriority:T,draftDue:x,addFocus:A,searchFocus:N,cannotAdd:M.computed(()=>!f.value.trim()),isEmpty:M.computed(()=>!m.value&&y.value.length===0),emptyText:M.computed(()=>l.value.length?w.value==="open"&&!g.value.trim()?"Nothing open - everything is done.":"No tasks match. Change the filter or clear the search.":"No tasks yet. Add one above."),countText:M.computed(()=>{if(m.value)return"";const e=y.value.length,t=l.value.length;return e===t?`${t} task${t===1?"":"s"}`:`${e} of ${t}`})};p.add(M.effect(()=>U.write(n.value))),p.add(M.effect(()=>{l.value,m.peek()||it("#/todo")}));const _=(e,t)=>{n.value=K({...n.peek(),[e]:t})},C=e=>l.peek().find(t=>t.id===e),J=e=>{l.value=l.peek().map(t=>t.id===e.id?e:t)};async function V(){m.value=!0;try{const e=await v("/todo/items");l.value=Array.isArray(e)?e:e.data??[]}catch{E.toast("Failed to load tasks",{type:"error"})}finally{m.value=!1}}async function c(e,t,o){try{return J(await v(`/todo/items/${e}`,"PUT",t)),o&&E.toast(o,{type:"success"}),!0}catch{return E.toast("Failed to update task",{type:"error"}),!1}}async function Q(){const e=f.peek().trim();if(e)try{const t=await v("/todo/items","POST",{text:e,priority:T.peek(),status:"not-started",dueAt:x.peek()||null});l.value=[...l.peek(),t],f.value="",x.value="",T.value=n.peek().defaultPriority,A.value=!0}catch{E.toast("Failed to add task",{type:"error"})}}const X=()=>(Q(),!1);async function q(e){if(n.peek().confirmDelete&&!await E.confirm("Delete this task?"))return!1;try{return await v(`/todo/items/${e}`,"DELETE"),l.value=l.peek().filter(t=>t.id!==e),E.toast("Task deleted",{type:"success"}),!0}catch{return E.toast("Failed to delete task",{type:"error"}),!1}}async function Z(){const e=S.peek().completed;if(!(!e||!await E.confirm(`Delete ${e} completed task${e===1?"":"s"}?`)))try{await v("/todo/completed","DELETE"),l.value=l.peek().filter(t=>t.status!=="completed"),E.toast("Completed tasks cleared",{type:"success"})}catch{E.toast("Failed to clear completed tasks",{type:"error"})}}const F=e=>{const t=C(e);if(!t)return;const o=t.status==="completed"?"not-started":"completed";c(e,{status:o},o==="completed"?"Task completed":"Task reopened")};function Y(e){const t=C(e);if(!t)return;const o=E.slideover({title:"Edit task",size:"md",position:"right"}),s=z(`
|
|
10
|
+
<div class="todo-panel">
|
|
11
|
+
<div>
|
|
12
|
+
<label class="form-label" for="todo-edit-text">Task</label>
|
|
13
|
+
<textarea id="todo-edit-text" class="form-input" data-model="text.value"></textarea>
|
|
14
|
+
</div>
|
|
15
|
+
<div class="todo-panel-row">
|
|
16
|
+
<div>
|
|
17
|
+
<label class="form-label" for="todo-edit-status">Status</label>
|
|
18
|
+
<select id="todo-edit-status" class="form-input" data-model="status.value">
|
|
19
|
+
${B.map(a=>`<option value="${a}">${u[a]}</option>`).join("")}
|
|
20
|
+
</select>
|
|
21
|
+
</div>
|
|
22
|
+
<div>
|
|
23
|
+
<label class="form-label" for="todo-edit-priority">Priority</label>
|
|
24
|
+
<select id="todo-edit-priority" class="form-input" data-model="priority.value">
|
|
25
|
+
${P.map(a=>`<option value="${a}">${u[a]}</option>`).join("")}
|
|
26
|
+
</select>
|
|
27
|
+
</div>
|
|
28
|
+
</div>
|
|
29
|
+
<div>
|
|
30
|
+
<label class="form-label" for="todo-edit-due">Due
|
|
31
|
+
<span data-help="A calendar day. Past due and not completed shows the task as overdue."></span>
|
|
32
|
+
</label>
|
|
33
|
+
<input id="todo-edit-due" type="date" class="form-input" data-model="due.value">
|
|
34
|
+
<div class="todo-quick-due">
|
|
35
|
+
<button type="button" class="btn btn-sm btn-ghost" data-on-click="dueIn" data-days="0">Today</button>
|
|
36
|
+
<button type="button" class="btn btn-sm btn-ghost" data-on-click="dueIn" data-days="1">Tomorrow</button>
|
|
37
|
+
<button type="button" class="btn btn-sm btn-ghost" data-on-click="dueIn" data-days="7">Next week</button>
|
|
38
|
+
<button type="button" class="btn btn-sm btn-ghost" data-on-click="clearDue">No date</button>
|
|
39
|
+
</div>
|
|
40
|
+
</div>
|
|
41
|
+
<small class="todo-panel-meta">Ctrl+Enter saves. Added <span data-bind-text="created"></span>.</small>
|
|
42
|
+
<div class="todo-panel-actions">
|
|
43
|
+
<button type="button" class="btn btn-sm btn-ghost text-danger" data-on-click="del">
|
|
44
|
+
<span data-icon="trash" data-icon-size="13"></span> Delete
|
|
45
|
+
</button>
|
|
46
|
+
<span class="spacer"></span>
|
|
47
|
+
<button type="button" class="btn btn-sm btn-ghost" data-on-click="cancel">Cancel</button>
|
|
48
|
+
<button type="button" class="btn btn-sm btn-primary" data-on-click="save"
|
|
49
|
+
data-bind-disabled="cannotSave.value">Save</button>
|
|
50
|
+
</div>
|
|
51
|
+
</div>`),i={text:M.observable(t.text||""),status:M.observable(t.status),priority:M.observable(t.priority),due:M.observable(t.dueAt?String(t.dueAt).slice(0,10):""),created:t.createdAt?D(t.createdAt).format("D MMM YYYY"):"earlier"};i.cannotSave=M.computed(()=>!i.text.value.trim());const h=async()=>{await c(t.id,{text:i.text.peek().trim(),status:i.status.peek(),priority:i.priority.peek(),dueAt:i.due.peek()||null},"Task saved")&&o.close()};o.element.appendChild(s);const $=O(s),r=M.applyBindings(i,s,{methods:{save:h,cancel:()=>o.close(),del:async()=>{await q(t.id)&&o.close()},dueIn:a=>{i.due.value=k(Number(a.currentTarget.dataset.days))},clearDue:()=>{i.due.value=""}}});s.addEventListener("keydown",a=>{(a.ctrlKey||a.metaKey)&&a.key==="Enter"&&(a.preventDefault(),h())}),j(o,()=>{r.dispose(),$()}),o.open(),setTimeout(()=>s.querySelector("#todo-edit-text")?.focus(),60)}function tt(){const e=E.slideover({title:"Todo settings",size:"sm",position:"right"}),t=r=>r.map(a=>`<option value="${a}">${u[a]}</option>`).join(""),o=z(`
|
|
52
|
+
<div class="todo-panel">
|
|
53
|
+
<div>
|
|
54
|
+
<label class="form-label" for="todo-set-filter">Show on opening
|
|
55
|
+
<span data-help="Which tasks the list shows when you open Todo. The filter in the toolbar changes it for this visit only."></span>
|
|
56
|
+
</label>
|
|
57
|
+
<select id="todo-set-filter" class="form-input" data-model="defaultFilter.value">
|
|
58
|
+
${t(["open","all","not-started","in-progress","completed","overdue"])}
|
|
59
|
+
</select>
|
|
60
|
+
</div>
|
|
61
|
+
<div>
|
|
62
|
+
<label class="form-label" for="todo-set-sort">Sort by
|
|
63
|
+
<span data-help="Completed tasks always sit at the bottom, whatever the sort."></span>
|
|
64
|
+
</label>
|
|
65
|
+
<select id="todo-set-sort" class="form-input" data-model="sort.value">
|
|
66
|
+
<option value="created">Order added</option>
|
|
67
|
+
<option value="priority">Priority, highest first</option>
|
|
68
|
+
<option value="due">Due date, soonest first</option>
|
|
69
|
+
<option value="status">Status</option>
|
|
70
|
+
</select>
|
|
71
|
+
</div>
|
|
72
|
+
<div>
|
|
73
|
+
<label class="form-label" for="todo-set-priority">Priority for new tasks</label>
|
|
74
|
+
<select id="todo-set-priority" class="form-input" data-model="defaultPriority.value">
|
|
75
|
+
${t(P)}
|
|
76
|
+
</select>
|
|
77
|
+
</div>
|
|
78
|
+
<div class="todo-setting-row">
|
|
79
|
+
<label class="form-label" for="todo-set-relative" style="margin:0">Relative due dates
|
|
80
|
+
<span data-help="On: "Tomorrow", "In 3 days". Off: the date itself."></span>
|
|
81
|
+
</label>
|
|
82
|
+
<input id="todo-set-relative" type="checkbox" class="form-switch" data-model="relativeDue.value">
|
|
83
|
+
</div>
|
|
84
|
+
<div class="todo-setting-row">
|
|
85
|
+
<label class="form-label" for="todo-set-confirm" style="margin:0">Confirm before deleting
|
|
86
|
+
<span data-help="Off deletes straight away - there is no undo."></span>
|
|
87
|
+
</label>
|
|
88
|
+
<input id="todo-set-confirm" type="checkbox" class="form-switch" data-model="confirmDelete.value">
|
|
89
|
+
</div>
|
|
90
|
+
<small class="todo-panel-meta">Saved in this browser as you change them.</small>
|
|
91
|
+
<div class="todo-panel-actions">
|
|
92
|
+
<button type="button" class="btn btn-sm btn-ghost" data-on-click="reset">Reset to defaults</button>
|
|
93
|
+
<span class="spacer"></span>
|
|
94
|
+
<button type="button" class="btn btn-sm btn-primary" data-on-click="done">Done</button>
|
|
95
|
+
</div>
|
|
96
|
+
</div>`),s=r=>M.computed({read:()=>n.value[r],write:a=>_(r,a)}),i=Object.fromEntries(Object.keys(H).map(r=>[r,s(r)]));e.element.appendChild(o);const h=O(o),$=M.applyBindings(i,o,{methods:{done:()=>e.close(),reset:()=>{n.value={...H}}}});j(e,()=>{$.dispose(),h()}),e.open()}st(b,{id:"todo-settings",icon:"settings",title:"Todo settings",onClick:tt}),d.querySelector(".todo-rows").innerHTML=vt,p.add(O(d,{position:"bottom"}));const et=M.applyBindings(W,d,{methods:{add:X,clearCompleted:Z}});p.add(()=>et.dispose());const L=d.querySelector(".todo-list"),ot=E.contextMenu(L,{match:".todo-row",inherit:!1,density:"compact",items:e=>{const t=e.dataset.id,o=C(t);return o?[{label:"Edit\u2026",icon:"edit",action:()=>Y(t)},{label:o.status==="completed"?"Reopen":"Complete",icon:"check",action:()=>F(t)},{type:"divider"},{label:"Status",icon:"circle",submenu:B.map(s=>({label:u[s],disabled:o.status===s,action:()=>c(t,{status:s})}))},{label:"Priority",icon:"flag",submenu:P.map(s=>({label:u[s],disabled:o.priority===s,action:()=>c(t,{priority:s})}))},{label:"Due",icon:"calendar",submenu:[{label:"Today",action:()=>c(t,{dueAt:k(0)})},{label:"Tomorrow",action:()=>c(t,{dueAt:k(1)})},{label:"Next week",action:()=>c(t,{dueAt:k(7)})},{type:"divider"},{label:"No due date",disabled:!o.dueAt,action:()=>c(t,{dueAt:null})}]},{type:"divider"},{label:"Delete",icon:"trash",danger:!0,action:()=>q(t)}]:[]}});p.add(()=>ot?.destroy?.()),L.addEventListener("click",e=>{const t=e.target.closest(".todo-row");if(t){if(e.target.closest(".todo-kebab, .todo-pill"))return lt(e.target.closest(".todo-kebab, .todo-pill"));e.target.closest(".todo-text")&&Y(t.dataset.id)}}),L.addEventListener("change",e=>{e.target.matches(".todo-check")&&F(e.target.closest(".todo-row").dataset.id)});const R=e=>{e.ctrlKey||e.metaKey||e.altKey||e.target.closest?.("input, textarea, select, [contenteditable], .dm-slideover")||(e.key==="/"?(e.preventDefault(),N.value=!0):(e.key==="n"||e.key==="N")&&(e.preventDefault(),A.value=!0))};document.addEventListener("keydown",R),p.add(()=>document.removeEventListener("keydown",R)),I.scan(d),await V()}};
|
package/bin/cli.js
CHANGED
|
@@ -9,6 +9,7 @@ import {spawnSync} from 'node:child_process';
|
|
|
9
9
|
import path from 'node:path';
|
|
10
10
|
import {fileURLToPath} from 'node:url';
|
|
11
11
|
import {composeSmtp} from './lib/smtp-defaults.js';
|
|
12
|
+
import {buildGitignore} from '../server/services/siteGitignore.js';
|
|
12
13
|
|
|
13
14
|
// ---------------------------------------------------------------------------
|
|
14
15
|
// Paths
|
|
@@ -55,6 +56,7 @@ if (flags.has('--help') || args.includes('-h')) {
|
|
|
55
56
|
--no-backup Skip creating a backup before updating
|
|
56
57
|
--no-install Skip npm install after updating
|
|
57
58
|
--dry-run Preview what would change without writing anything
|
|
59
|
+
--force Re-install even when the installed version already matches
|
|
58
60
|
|
|
59
61
|
Examples:
|
|
60
62
|
npx domma-cms my-blog
|
|
@@ -62,6 +64,7 @@ if (flags.has('--help') || args.includes('-h')) {
|
|
|
62
64
|
npx domma-cms update
|
|
63
65
|
npx domma-cms update --dry-run
|
|
64
66
|
npx domma-cms update --yes --no-backup
|
|
67
|
+
npx domma-cms update --yes --force
|
|
65
68
|
`);
|
|
66
69
|
process.exit(0);
|
|
67
70
|
}
|
|
@@ -322,16 +325,9 @@ done();
|
|
|
322
325
|
// Write .gitignore
|
|
323
326
|
// ---------------------------------------------------------------------------
|
|
324
327
|
|
|
325
|
-
|
|
326
|
-
.
|
|
327
|
-
|
|
328
|
-
content/media/
|
|
329
|
-
*.log
|
|
330
|
-
.domma-backups/
|
|
331
|
-
|
|
332
|
-
# Analytics plugin runtime counters (written on every page hit). Never track.
|
|
333
|
-
plugins/analytics/data/
|
|
334
|
-
`;
|
|
328
|
+
// The content-only allowlist: this repo tracks the site's content, never the
|
|
329
|
+
// engine or distributed plugins. Same rules the manager's scaffolder writes.
|
|
330
|
+
const GITIGNORE = buildGitignore();
|
|
335
331
|
|
|
336
332
|
step('Writing .gitignore');
|
|
337
333
|
writeFileSync(path.join(target, '.gitignore'), GITIGNORE, 'utf8');
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plugin versions, for the engine updater.
|
|
3
|
+
*
|
|
4
|
+
* The updater replaces each bundled plugin (Blog, Mail Reader) on a site with
|
|
5
|
+
* the engine's copy. A site can hold a NEWER copy than the engine - installed
|
|
6
|
+
* from the marketplace catalogue, where the plugin is written - and replacing
|
|
7
|
+
* that would quietly take the site back a version. So the updater asks first.
|
|
8
|
+
*
|
|
9
|
+
* @module bin/lib/plugin-version
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/** -1, 0 or 1. Numeric per dot-separated part; a missing part is 0; junk sorts lowest. */
|
|
13
|
+
export function compareVersions(a, b) {
|
|
14
|
+
const parse = (v) => String(v ?? '').split(/[.+-]/).slice(0, 3).map(n => (/^\d+$/.test(n) ? Number(n) : -1));
|
|
15
|
+
const x = parse(a);
|
|
16
|
+
const y = parse(b);
|
|
17
|
+
for (let i = 0; i < 3; i++) {
|
|
18
|
+
const d = (x[i] ?? 0) - (y[i] ?? 0);
|
|
19
|
+
if (d) return d > 0 ? 1 : -1;
|
|
20
|
+
}
|
|
21
|
+
return 0;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Should the engine's copy replace the site's? Not when the site's is newer. */
|
|
25
|
+
export function shouldReplacePlugin(siteVersion, engineVersion) {
|
|
26
|
+
if (!siteVersion) return true;
|
|
27
|
+
return compareVersions(siteVersion, engineVersion) <= 0;
|
|
28
|
+
}
|
package/bin/update.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
3
|
* Domma CMS - Project Update
|
|
4
|
-
* Usage: npx domma-cms update [--yes] [--no-backup] [--no-install] [--dry-run]
|
|
4
|
+
* Usage: npx domma-cms update [--yes] [--no-backup] [--no-install] [--dry-run] [--force]
|
|
5
5
|
*
|
|
6
6
|
* Run from inside an existing Domma CMS project directory.
|
|
7
7
|
* Pulls upstream changes from the installed CLI package into the project,
|
|
@@ -14,6 +14,7 @@ import path from 'node:path';
|
|
|
14
14
|
import {createInterface} from 'node:readline';
|
|
15
15
|
import {fileURLToPath} from 'node:url';
|
|
16
16
|
import {deepMergeNewKeys} from './lib/config-merge.js';
|
|
17
|
+
import {shouldReplacePlugin} from './lib/plugin-version.js';
|
|
17
18
|
|
|
18
19
|
// ---------------------------------------------------------------------------
|
|
19
20
|
// Paths
|
|
@@ -115,6 +116,12 @@ export default async function update(_positional, flags) {
|
|
|
115
116
|
const isDryRun = flags.has('--dry-run');
|
|
116
117
|
const skipConfirm = flags.has('--yes');
|
|
117
118
|
const skipBackup = flags.has('--no-backup');
|
|
119
|
+
// Re-install the SAME version. The version check below exists to make a
|
|
120
|
+
// routine `update` cheap, but it also means a bad build that shipped under
|
|
121
|
+
// a good version number cannot be corrected by re-rolling it - the updater
|
|
122
|
+
// reports "already up to date" and copies nothing. --force is how you
|
|
123
|
+
// repair an install whose files are wrong but whose .domma marker is right.
|
|
124
|
+
const force = flags.has('--force');
|
|
118
125
|
const skipInstall = flags.has('--no-install');
|
|
119
126
|
|
|
120
127
|
const cwd = process.cwd();
|
|
@@ -184,11 +191,16 @@ export default async function update(_positional, flags) {
|
|
|
184
191
|
info(`Available: ${incomingVersion}`);
|
|
185
192
|
console.log('');
|
|
186
193
|
|
|
187
|
-
if (installedVersion !== 'unknown' && installedVersion === incomingVersion) {
|
|
194
|
+
if (installedVersion !== 'unknown' && installedVersion === incomingVersion && !force) {
|
|
188
195
|
info('✓ Already up to date.');
|
|
196
|
+
info(' (re-install these files anyway with --force)');
|
|
189
197
|
console.log('');
|
|
190
198
|
process.exit(0);
|
|
191
199
|
}
|
|
200
|
+
if (force && installedVersion === incomingVersion) {
|
|
201
|
+
warn(`Re-installing ${incomingVersion} over itself (--force).`);
|
|
202
|
+
console.log('');
|
|
203
|
+
}
|
|
192
204
|
|
|
193
205
|
// -------------------------------------------------------------------------
|
|
194
206
|
// 3. Dry-run mode - preview changes and exit
|
|
@@ -368,6 +380,19 @@ export default async function update(_positional, flags) {
|
|
|
368
380
|
}
|
|
369
381
|
newPlugins.push(name);
|
|
370
382
|
} else {
|
|
383
|
+
// A site can hold a NEWER copy than the engine - installed from
|
|
384
|
+
// the marketplace catalogue, where the plugin is written.
|
|
385
|
+
// Replacing it would take the site back a version, so keep it.
|
|
386
|
+
const versionOf = (dir) => {
|
|
387
|
+
try { return readJson(path.join(dir, 'plugin.json')).version ?? null; } catch { return null; }
|
|
388
|
+
};
|
|
389
|
+
const siteVersion = versionOf(dstPlugin);
|
|
390
|
+
const engineVersion = versionOf(srcPlugin);
|
|
391
|
+
if (!shouldReplacePlugin(siteVersion, engineVersion)) {
|
|
392
|
+
info(` → plugins/${name}/ kept: ${siteVersion} here is newer than the engine's ${engineVersion}`);
|
|
393
|
+
continue;
|
|
394
|
+
}
|
|
395
|
+
|
|
371
396
|
// Existing plugin - identify paths to preserve
|
|
372
397
|
let preservePaths = new Set(['data']);
|
|
373
398
|
try {
|