domma-cms 0.2.1 → 0.5.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/README.md +3 -3
- package/admin/css/admin.css +1 -1200
- package/admin/dist/domma/domma-tools.css +2313 -0
- package/admin/dist/domma/domma-tools.min.js +10 -0
- package/admin/index.html +4 -0
- package/admin/js/api.js +1 -242
- package/admin/js/app.js +9 -279
- package/admin/js/config/sidebar-config.js +1 -115
- package/admin/js/lib/card.js +1 -63
- package/admin/js/lib/image-editor.js +1 -869
- package/admin/js/lib/markdown-toolbar.js +54 -421
- package/admin/js/templates/action-editor.html +171 -0
- package/admin/js/templates/actions-list.html +19 -0
- package/admin/js/templates/api-reference.html +1411 -0
- package/admin/js/templates/block-editor.html +158 -0
- package/admin/js/templates/blocks.html +8 -0
- package/admin/js/templates/collection-editor.html +47 -0
- package/admin/js/templates/collection-entries.html +3 -0
- package/admin/js/templates/collections.html +51 -4
- package/admin/js/templates/documentation.html +258 -0
- package/admin/js/templates/form-editor.html +238 -0
- package/{plugins/form-builder/admin → admin/js}/templates/form-submissions.html +30 -30
- package/{plugins/form-builder/admin/templates/forms-list.html → admin/js/templates/forms.html} +17 -17
- package/admin/js/templates/layouts.html +44 -7
- package/admin/js/templates/login.html +29 -4
- package/admin/js/templates/my-profile.html +17 -0
- package/admin/js/templates/page-editor.html +48 -0
- package/admin/js/templates/pages.html +6 -1
- package/admin/js/templates/pro-docs.html +259 -0
- package/admin/js/templates/role-editor.html +59 -0
- package/admin/js/templates/roles.html +10 -0
- package/admin/js/templates/settings.html +137 -18
- package/admin/js/templates/tutorials.html +81 -0
- package/admin/js/templates/user-editor.html +7 -0
- package/admin/js/templates/users.html +3 -1
- package/admin/js/templates/view-editor.html +201 -0
- package/admin/js/templates/view-preview.html +51 -0
- package/admin/js/templates/views-list.html +19 -0
- package/admin/js/views/action-editor.js +1 -0
- package/admin/js/views/actions-list.js +1 -0
- package/admin/js/views/api-reference.js +1 -0
- package/admin/js/views/block-editor.js +8 -0
- package/admin/js/views/blocks.js +4 -0
- package/admin/js/views/collection-editor.js +3 -487
- package/admin/js/views/collection-entries.js +1 -484
- package/admin/js/views/collections.js +1 -153
- package/admin/js/views/dashboard.js +1 -56
- package/admin/js/views/documentation.js +1 -12
- package/admin/js/views/form-editor.js +8 -0
- package/admin/js/views/form-submissions.js +1 -0
- package/admin/js/views/forms.js +1 -0
- package/admin/js/views/index.js +1 -39
- package/admin/js/views/layouts.js +9 -42
- package/admin/js/views/login.js +7 -251
- package/admin/js/views/media.js +1 -240
- package/admin/js/views/my-profile.js +1 -0
- package/admin/js/views/navigation.js +14 -212
- package/admin/js/views/page-editor.js +72 -661
- package/admin/js/views/pages.js +5 -72
- package/admin/js/views/plugins.js +13 -90
- package/admin/js/views/pro-docs.js +1 -0
- package/admin/js/views/role-editor.js +1 -0
- package/admin/js/views/roles.js +4 -0
- package/admin/js/views/settings.js +3 -199
- package/admin/js/views/tutorials.js +1 -12
- package/admin/js/views/user-editor.js +1 -88
- package/admin/js/views/users.js +4 -76
- package/admin/js/views/view-editor.js +1 -0
- package/admin/js/views/view-preview.js +1 -0
- package/admin/js/views/views-list.js +1 -0
- package/bin/cli.js +1 -1
- package/config/auth.json +2 -17
- package/config/connections.json.bak +9 -0
- package/config/connections.json.example +9 -0
- package/config/navigation.json +15 -0
- package/config/plugins.json +19 -29
- package/config/server.json +6 -6
- package/config/site.json +17 -6
- package/package.json +24 -10
- package/plugins/domma-effects/public/celebrations/core/canvas.js +2 -104
- package/plugins/domma-effects/public/celebrations/core/particles.js +1 -144
- package/plugins/domma-effects/public/celebrations/core/physics.js +1 -166
- package/plugins/domma-effects/public/celebrations/index.js +1 -535
- package/plugins/domma-effects/public/celebrations/themes/christmas.js +1 -1805
- package/plugins/domma-effects/public/celebrations/themes/guy-fawkes.js +1 -1477
- package/plugins/domma-effects/public/celebrations/themes/halloween.js +1 -1837
- package/plugins/domma-effects/public/celebrations/themes/st-andrews.js +1 -1175
- package/plugins/domma-effects/public/celebrations/themes/st-davids.js +1 -1258
- package/plugins/domma-effects/public/celebrations/themes/st-georges.js +1 -1754
- package/plugins/domma-effects/public/celebrations/themes/st-patricks.js +1 -1290
- package/plugins/domma-effects/public/celebrations/themes/valentines.js +1 -1361
- package/plugins/example-analytics/stats.json +21 -12
- package/plugins/theme-roller/admin/templates/theme-roller.html +71 -0
- package/plugins/theme-roller/admin/views/theme-roller-view.js +403 -0
- package/plugins/theme-roller/config.js +1 -0
- package/plugins/theme-roller/plugin.js +233 -0
- package/plugins/theme-roller/plugin.json +31 -0
- package/plugins/theme-roller/public/active-theme.css +0 -0
- package/plugins/theme-roller/public/inject-head-late.html +1 -0
- package/public/css/forms.css +1 -0
- package/public/css/site.css +1 -302
- package/public/js/btt.js +1 -90
- package/public/js/cookie-consent.js +1 -61
- package/public/js/form-logic-engine.js +1 -0
- package/public/js/forms.js +1 -0
- package/public/js/site.js +1 -204
- package/scripts/build.js +194 -129
- package/scripts/pro.js +254 -0
- package/scripts/reset.js +33 -8
- package/scripts/seed.js +343 -78
- package/scripts/setup.js +5 -4
- package/server/middleware/auth.js +136 -97
- package/server/routes/api/actions.js +200 -0
- package/server/routes/api/auth.js +292 -116
- package/server/routes/api/blocks.js +84 -0
- package/server/routes/api/collections.js +88 -23
- package/{plugins/form-builder/plugin.js → server/routes/api/forms.js} +483 -505
- package/server/routes/api/layouts.js +49 -25
- package/server/routes/api/media.js +118 -93
- package/server/routes/api/navigation.js +40 -37
- package/server/routes/api/pages.js +132 -118
- package/server/routes/api/plugins.js +6 -3
- package/server/routes/api/settings.js +104 -89
- package/server/routes/api/users.js +27 -21
- package/server/routes/api/views.js +148 -0
- package/server/routes/public.js +124 -108
- package/server/server.js +269 -173
- package/server/services/actions.js +387 -0
- package/server/services/adapterRegistry.js +98 -0
- package/server/services/adapters/FileAdapter.js +192 -0
- package/server/services/adapters/MongoAdapter.js +220 -0
- package/server/services/blocks.js +162 -0
- package/server/services/collections.js +74 -86
- package/server/services/connectionManager.js +102 -0
- package/server/services/content.js +312 -307
- package/{plugins/form-builder → server/services}/email.js +126 -103
- package/server/services/forms.js +173 -0
- package/server/services/markdown.js +1378 -648
- package/server/services/permissionRegistry.js +173 -0
- package/server/services/presetCollections.js +251 -0
- package/server/services/renderer.js +75 -1
- package/server/services/roles.js +227 -0
- package/server/services/rowAccess.js +104 -0
- package/server/services/userProfiles.js +199 -0
- package/server/services/users.js +281 -212
- package/server/services/views.js +280 -0
- package/server/templates/page.html +119 -113
- package/plugins/form-builder/admin/templates/form-editor.html +0 -171
- package/plugins/form-builder/admin/templates/form-settings.html +0 -29
- package/plugins/form-builder/admin/views/form-editor.js +0 -1442
- package/plugins/form-builder/admin/views/form-settings.js +0 -38
- package/plugins/form-builder/admin/views/form-submissions.js +0 -295
- package/plugins/form-builder/admin/views/forms-list.js +0 -164
- package/plugins/form-builder/config.js +0 -9
- package/plugins/form-builder/data/forms/consent.json +0 -104
- package/plugins/form-builder/data/forms/contact-details.json +0 -63
- package/plugins/form-builder/data/forms/contacts.json +0 -66
- package/plugins/form-builder/data/submissions/consent.json +0 -13
- package/plugins/form-builder/data/submissions/contact-details.json +0 -1
- package/plugins/form-builder/data/submissions/contacts.json +0 -26
- package/plugins/form-builder/plugin.json +0 -52
- package/plugins/form-builder/public/form-logic-engine.js +0 -568
- package/plugins/form-builder/public/inject-body.html +0 -352
- package/plugins/form-builder/public/inject-head.html +0 -58
- package/plugins/form-builder/public/package.json +0 -1
- package/scripts/copy-domma.js +0 -48
package/admin/js/views/users.js
CHANGED
|
@@ -1,76 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
import {api, getUser} from '../api.js';
|
|
6
|
-
|
|
7
|
-
const ROLE_BADGE = {
|
|
8
|
-
admin: 'badge-danger',
|
|
9
|
-
manager: 'badge-warning',
|
|
10
|
-
editor: 'badge-info',
|
|
11
|
-
subscriber: 'badge-secondary'
|
|
12
|
-
};
|
|
13
|
-
|
|
14
|
-
export const usersView = {
|
|
15
|
-
templateUrl: '/admin/js/templates/users.html',
|
|
16
|
-
|
|
17
|
-
async onMount($container) {
|
|
18
|
-
const currentUser = getUser();
|
|
19
|
-
let users = await api.users.list().catch(() => []);
|
|
20
|
-
|
|
21
|
-
const renderTable = (data) => {
|
|
22
|
-
T.create('#users-table', {
|
|
23
|
-
data,
|
|
24
|
-
columns: [
|
|
25
|
-
{key: 'name', title: 'Name'},
|
|
26
|
-
{key: 'email', title: 'Email'},
|
|
27
|
-
{
|
|
28
|
-
key: 'role', title: 'Role',
|
|
29
|
-
render: (val) => `<span class="badge ${ROLE_BADGE[val] || 'badge-secondary'}">${val}</span>` },
|
|
30
|
-
{
|
|
31
|
-
key: 'isActive', title: 'Status',
|
|
32
|
-
render: (val) => val
|
|
33
|
-
? '<span class="badge badge-success">Active</span>'
|
|
34
|
-
: '<span class="badge badge-secondary">Inactive</span>' },
|
|
35
|
-
{
|
|
36
|
-
key: 'lastLogin', title: 'Last login',
|
|
37
|
-
render: (val) => val ? D(val).format('DD MMM YYYY HH:mm') : 'Never' },
|
|
38
|
-
{
|
|
39
|
-
key: 'actions', title: 'Actions',
|
|
40
|
-
render: (_, row) => {
|
|
41
|
-
const isSelf = row.id === currentUser?.id;
|
|
42
|
-
return `
|
|
43
|
-
<a href="#/users/edit/${row.id}" class="btn btn-sm btn-outline">Edit</a>
|
|
44
|
-
${!isSelf ? `<button class="btn btn-sm btn-danger btn-delete-user" data-id="${row.id}" data-name="${escapeAttr(row.name)}">Delete</button>` : ''}
|
|
45
|
-
`;
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
],
|
|
49
|
-
emptyMessage: 'No users found.'
|
|
50
|
-
});
|
|
51
|
-
Domma.icons.scan();
|
|
52
|
-
Domma.effects.reveal('.card', { animation: 'fade', duration: 350 });
|
|
53
|
-
};
|
|
54
|
-
|
|
55
|
-
renderTable(users);
|
|
56
|
-
|
|
57
|
-
$container.off('click', '.btn-delete-user').on('click', '.btn-delete-user', async function () {
|
|
58
|
-
const id = $(this).data('id');
|
|
59
|
-
const name = $(this).data('name');
|
|
60
|
-
const confirmed = await E.confirm(`Delete user <strong>${name}</strong>? This cannot be undone.`);
|
|
61
|
-
if (!confirmed) return;
|
|
62
|
-
try {
|
|
63
|
-
await api.users.delete(id);
|
|
64
|
-
E.toast('User deleted.', { type: 'success' });
|
|
65
|
-
users = users.filter(u => u.id !== id);
|
|
66
|
-
renderTable(users);
|
|
67
|
-
} catch (err) {
|
|
68
|
-
E.toast(`Failed to delete: ${err.message}`, { type: 'error' });
|
|
69
|
-
}
|
|
70
|
-
});
|
|
71
|
-
}
|
|
72
|
-
};
|
|
73
|
-
|
|
74
|
-
function escapeAttr(str) {
|
|
75
|
-
return String(str).replace(/"/g, '"');
|
|
76
|
-
}
|
|
1
|
+
import{api as c,getUser as p}from"../api.js";export const usersView={templateUrl:"/admin/js/templates/users.html",async onMount(a){const i=p(),b=E.loader(a.get(0),{type:"dots"});let[n,g]=await Promise.all([c.users.list().catch(()=>[]),c.get("/collections/roles/entries?limit=100").catch(()=>({entries:[]}))]);b.destroy();const o=g.entries||[],d=r=>{T.create("#users-table",{data:r,columns:[{key:"name",title:"Name"},{key:"email",title:"Email"},{key:"role",title:"Role",render:e=>{const t=o.find(m=>m.data?.name===e),s=t?.data?.badgeClass||"badge-secondary",l=t?.data?.label||e;return`<span class="badge ${s}">${l}</span>`}},{key:"isActive",title:"Status",render:e=>e?'<span class="badge badge-success">Active</span>':'<span class="badge badge-secondary">Inactive</span>'},{key:"lastLogin",title:"Last login",render:e=>e?D(e).format("DD MMM YYYY HH:mm"):"Never"},{key:"actions",title:"Actions",render:(e,t)=>{const s=t.id===i?.id,l=Object.fromEntries(o.map(u=>[u.data?.name,u.data?.level??99]));return(l[i?.role]??99)<(l[t.role]??99)?`
|
|
2
|
+
<a href="#/users/edit/${t.id}" class="btn btn-sm btn-primary">Edit</a>
|
|
3
|
+
${s?"":`<button class="btn btn-sm btn-danger btn-delete-user" data-id="${t.id}" data-name="${y(t.name)}">Delete</button>`}
|
|
4
|
+
`:""}}],emptyMessage:"No users found."}),Domma.icons.scan()};d(n),a.off("click",".btn-delete-user").on("click",".btn-delete-user",async function(){const r=$(this).data("id"),e=$(this).data("name");if(await E.confirm(`Delete user <strong>${e}</strong>? This cannot be undone.`))try{await c.delete(`/users/${r}`),E.toast("User deleted.",{type:"success"}),n=n.filter(s=>s.id!==r),d(n)}catch(s){E.toast(`Failed to delete: ${s.message}`,{type:"error"})}})}};function y(a){return String(a).replace(/"/g,""")}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{api as h}from"../api.js";let b=null,f=null;export const viewEditorView={templateUrl:"/admin/js/templates/view-editor.html",async onMount(e){b=null,f=null;const l=window.location.hash.match(/\/views\/edit\/([^/?#]+)/);l&&(b=l[1]),E.tabs(e.find("#view-editor-tabs").get(0)),await O(e),await D(e),await j(e),await T(e),e.find("#view-source").get(0)?.addEventListener("change",async()=>{await x(e)}),b&&(e.find("#view-editor-title").text("Edit View"),await I(e,b)),e.find("#add-stage-btn").off("click").on("click",()=>{const n=e.find("#add-stage-type").val()||"$match";q(e,{type:n,config:{}})}),e.find("#save-view-btn").off("click").on("click",async()=>{await Q(e)}),H(e),Y(e),Domma.icons.scan()}};async function O(e){const t=e.find("#view-source").get(0);if(t)try{(await h.collections.list()).forEach(n=>{const a=document.createElement("option");a.value=n.slug,a.textContent=`${n.title} (${n.slug})`,t.appendChild(a)})}catch{}}async function T(e){const t=e.find("#view-block-name").get(0);if(t)try{(await h.blocks.list()).forEach(n=>{const a=document.createElement("option");a.value=n.name,a.textContent=`${n.name}.html`,t.appendChild(a)})}catch{}}async function D(e){const t=e.find("#view-connection").get(0);if(t)try{const l=await h.collections.getConnections();Object.keys(l).forEach(n=>{if(!t.querySelector(`option[value="${n}"]`)){const a=document.createElement("option");a.value=n,a.textContent=n,t.appendChild(a)}})}catch{}}async function j(e){const t=e.find("#view-roles-checkboxes").get(0);if(!t)return;["admin","manager","editor","subscriber"].forEach(n=>{const a=document.createElement("label");a.style.cssText="display:flex;align-items:center;gap:.5rem;cursor:pointer;";const s=document.createElement("input");s.type="checkbox",s.value=n,s.dataset.role=n,s.className="view-role-cb",s.checked=n==="admin",a.appendChild(s),a.appendChild(document.createTextNode(n)),t.appendChild(a)})}async function x(e){const t=e.find("#view-source").val();if(!t){f=null;return}try{f=(await h.collections.get(t))?.fields||[]}catch{f=null}e.find(".stage-card[data-guided]").each(function(){const l=this.dataset.stageType,n=this.querySelector(".stage-guided");n&&(l==="$match"&&M(n),l==="$sort"&&z(n))}),A(e,null)}async function I(e,t){try{const l=await h.views.get(t);if(!l){E.toast("View not found.",{type:"error"}),R.navigate("/views");return}const n=l.pipeline?.source;n&&(e.find("#view-source").val(n),await x(e)),F(e,l)}catch(l){E.toast(l.message||"Failed to load view.",{type:"error"}),R.navigate("/views")}}function F(e,t){e.find("#view-title").val(t.title||""),e.find("#view-slug").val(t.slug||""),e.find("#view-description").val(t.description||""),e.find("#view-source").val(t.pipeline?.source||""),e.find("#view-connection").val(t.connection||"default"),e.find("#view-display-mode").val(t.display?.mode||"table"),e.find("#view-page-size").val(t.display?.pageSize||25),e.find("#view-block-name").val(t.display?.block||"");const l=t.access?.roles||["admin"];e.find(".view-role-cb").each(function(){this.checked=l.includes(this.value)}),e.find("#view-public").prop("checked",t.access?.public||!1);const n=t.access?.rowLevel;n&&(e.find("#view-rowlevel-enabled").prop("checked",!0),e.find("#view-rowlevel-config").css("display","flex"),e.find("#view-rowlevel-mode").val(n.mode||"owner"),e.find("#view-rowlevel-userkey").val(n.userKey||"id"),n.mode==="field"&&(e.find("#view-rowlevel-field-group").css("display",""),e.find("#view-rowlevel-field").val(n.field||"")));const a=e.find("#pipeline-stages-list").get(0);if(a)for(;a.firstChild;)a.removeChild(a.firstChild);(t.pipeline?.stages||[]).forEach(s=>q(e,s)),A(e,t.display?.columns||[]),S(e)}const _=[{value:"eq",label:"= equals"},{value:"ne",label:"\u2260 not equals"},{value:"gt",label:"> greater than"},{value:"lt",label:"< less than"},{value:"gte",label:"\u2265 greater or equal"},{value:"lte",label:"\u2264 less or equal"},{value:"contains",label:"~ contains (regex)"},{value:"in",label:"\u2208 in (comma list)"}];function g(e=!1){const t=[];return e&&t.push({value:"",label:"\u2014 select field \u2014"}),(f||[]).forEach(l=>{t.push({value:`data.${l.name}`,label:`${l.label||l.name} (${l.name})`})}),t.length===0||t.length===1&&e?(t.push({value:"__meta.createdAt",label:"Created At"}),t.push({value:"__meta.updatedAt",label:"Updated At"})):(t.push({value:"__meta.createdAt",label:"Created At (meta)"}),t.push({value:"__meta.updatedAt",label:"Updated At (meta)"})),t}function v(e,t){const l=document.createElement("select");return l.className="form-input form-input--sm",e.forEach(n=>{const a=document.createElement("option");a.value=n.value,a.textContent=n.label,String(n.value)===String(t)&&(a.selected=!0),l.appendChild(a)}),l}function N(e,t,l="text"){const n=document.createElement("input");return n.type=l,n.className="form-input form-input--sm",n.placeholder=e,n.value=t??"",n}function B(e){if(!f?.length)return null;const t=e?.startsWith("data.")?e.slice(5):null;return t&&f.find(l=>l.name===t)||null}function y(e,t,l=""){const n=B(e);if(n?.type==="select"&&(t==="eq"||t==="ne")&&n.options?.length){const i=document.createElement("select");i.className="form-input form-input--sm match-val",i.style.flex="1";const o=document.createElement("option");return o.value="",o.textContent="\u2014 select value \u2014",i.appendChild(o),(n.options||[]).forEach(c=>{const d=typeof c=="string"?c:c.value??"",u=typeof c=="string"?c:c.label||d;if(!d||d==="undefined")return;const m=document.createElement("option");m.value=d,m.textContent=u,d===l&&(m.selected=!0),i.appendChild(m)}),i}const s=t==="in"?"val1, val2, val3":t==="contains"?"search pattern (regex)":t==="gt"||t==="lt"||t==="gte"||t==="lte"?"0":"value",r=document.createElement("input");return r.type="text",r.className="form-input form-input--sm match-val",r.style.flex="1",r.placeholder=s,r.value=l,r}function k(e){const t=[],l={$eq:"eq",$ne:"ne",$gt:"gt",$lt:"lt",$gte:"gte",$lte:"lte",$in:"in"};return Object.entries(e||{}).forEach(([n,a])=>{if(typeof a=="object"&&a!==null&&!Array.isArray(a)){if("$regex"in a){t.push({field:n,op:"contains",val:String(a.$regex??"")});return}Object.entries(a).forEach(([s,r])=>{const i=l[s];i&&t.push({field:n,op:i,val:Array.isArray(r)?r.join(", "):String(r)})})}else t.push({field:n,op:"eq",val:String(a??"")})}),t}function C(e,t,l="",n="eq",a=""){const s=document.createElement("div");s.className="match-condition-row",s.style.cssText="display:flex;gap:.4rem;align-items:center;margin-bottom:.4rem;flex-wrap:wrap;";const r=v([{value:"",label:"\u2014 field \u2014"},...g()],l);r.className+=" match-field",r.style.minWidth="140px";const i=v(_,n);i.className+=" match-op",i.style.minWidth="160px";const o=document.createElement("div");o.className="match-val-wrap",o.style.cssText="flex:1;min-width:120px;display:flex;",o.appendChild(y(l,n,a));const c=()=>{const m=o.querySelector(".match-val")?.value??"";for(;o.firstChild;)o.removeChild(o.firstChild);o.appendChild(y(r.value,i.value,m))};r.addEventListener("change",c),i.addEventListener("change",c);const d=document.createElement("button");d.type="button",d.className="btn btn-sm btn-ghost",d.title="Remove";const u=document.createElement("span");u.setAttribute("data-icon","x"),d.appendChild(u),d.addEventListener("click",()=>s.remove()),s.appendChild(r),s.appendChild(i),s.appendChild(o),s.appendChild(d),e.insertBefore(s,t),Domma.icons.scan(s)}function W(e,t={}){for(;e.firstChild;)e.removeChild(e.firstChild);const l=t.$match||t||{},n="$or"in l;let a=[];n?(l.$or||[]).forEach(d=>k(d).forEach(u=>a.push(u))):a=k(l);const s=document.createElement("div");s.style.cssText="display:flex;align-items:center;gap:.5rem;margin-bottom:.6rem;";const r=document.createElement("label");r.className="form-label",r.style.marginBottom="0",r.textContent="Match:";const i=document.createElement("select");i.className="form-input form-input--sm match-logic",i.style.width="auto",[{value:"and",label:"ALL conditions (AND)"},{value:"or",label:"ANY condition (OR)"}].forEach(d=>{const u=document.createElement("option");u.value=d.value,u.textContent=d.label,(n?"or":"and")===d.value&&(u.selected=!0),i.appendChild(u)}),s.appendChild(r),s.appendChild(i),e.appendChild(s);const o=document.createElement("button");o.type="button",o.className="btn btn-ghost btn-sm match-add-btn";const c=document.createElement("span");c.setAttribute("data-icon","plus"),o.appendChild(c),o.appendChild(document.createTextNode(" Add Condition")),o.addEventListener("click",()=>{C(e,o),Domma.icons.scan(e)}),e.appendChild(o),a.forEach(d=>C(e,o,d.field,d.op,d.val)),a.length===0&&C(e,o),Domma.icons.scan(e)}function M(e){e.querySelectorAll(".match-condition-row").forEach(t=>{const l=t.querySelector(".match-field");if(l){const r=l.value,i=v([{value:"",label:"\u2014 field \u2014"},...g()],r);i.className=l.className,i.style.cssText=l.style.cssText;const o=t.querySelector(".match-val-wrap"),c=t.querySelector(".match-op");o&&c&&i.addEventListener("change",()=>{const d=o.querySelector(".match-val")?.value??"";for(;o.firstChild;)o.removeChild(o.firstChild);o.appendChild(y(i.value,c.value,d))}),l.parentNode.replaceChild(i,l)}const n=t.querySelector(".match-val-wrap"),a=t.querySelector(".match-field")?.value,s=t.querySelector(".match-op")?.value;if(n&&a&&s){const r=n.querySelector(".match-val")?.value??"";for(;n.firstChild;)n.removeChild(n.firstChild);n.appendChild(y(a,s,r))}})}function U(e){const t=e.querySelector(".match-logic")?.value||"and",l=a=>{const s=a.querySelector(".match-field")?.value?.trim(),r=a.querySelector(".match-op")?.value,i=a.querySelector(".match-val")?.value?.trim()??"";if(!s)return null;const o=s.startsWith("__")?s.slice(2):s,c={};switch(r){case"eq":c[o]=i;break;case"ne":c[o]={$ne:i};break;case"gt":c[o]={$gt:isNaN(i)?i:Number(i)};break;case"lt":c[o]={$lt:isNaN(i)?i:Number(i)};break;case"gte":c[o]={$gte:isNaN(i)?i:Number(i)};break;case"lte":c[o]={$lte:isNaN(i)?i:Number(i)};break;case"contains":c[o]={$regex:i,$options:"i"};break;case"in":c[o]={$in:i.split(",").map(d=>d.trim()).filter(Boolean)};break}return Object.keys(c).length?c:null},n=[];return e.querySelectorAll(".match-condition-row").forEach(a=>{const s=l(a);s&&n.push(s)}),n.length===0?{}:t==="or"?{$or:n}:Object.assign({},...n)}function V(e,t={}){for(;e.firstChild;)e.removeChild(e.firstChild);const l=t.$sort||t||{},n=Object.entries(l),a=document.createElement("button");a.type="button",a.className="btn btn-ghost btn-sm";const s=document.createElement("span");s.setAttribute("data-icon","plus"),a.appendChild(s),a.appendChild(document.createTextNode(" Add Sort")),e.appendChild(a);const r=(i="",o=1)=>{const c=document.createElement("div");c.className="sort-row",c.style.cssText="display:flex;gap:.4rem;align-items:center;margin-bottom:.4rem;";const d=v([{value:"",label:"\u2014 field \u2014"},...g()],i);d.className+=" sort-field";const u=v([{value:"1",label:"\u2191 Ascending"},{value:"-1",label:"\u2193 Descending"}],String(o));u.className+=" sort-dir";const m=document.createElement("button");m.type="button",m.className="btn btn-sm btn-ghost",m.title="Remove";const p=document.createElement("span");p.setAttribute("data-icon","x"),m.appendChild(p),m.addEventListener("click",()=>{c.remove()}),c.appendChild(d),c.appendChild(u),c.appendChild(m),e.insertBefore(c,a),Domma.icons.scan(c)};a.addEventListener("click",()=>{r()}),n.forEach(([i,o])=>r(i,o)),n.length===0&&r(),Domma.icons.scan(e)}function z(e){e.querySelectorAll(".sort-row").forEach(t=>{const l=t.querySelector(".sort-field"),n=l?.value,a=v([{value:"",label:"\u2014 field \u2014"},...g()],n);a.className=l.className,l.parentNode.replaceChild(a,l)})}function J(e){const t={};return e.querySelectorAll(".sort-row").forEach(l=>{const n=l.querySelector(".sort-field")?.value?.trim(),a=parseInt(l.querySelector(".sort-dir")?.value,10)||1;if(!n)return;const s=n.startsWith("__")?n.slice(2):n;t[s]=a}),t}function q(e,t){const l=e.find("#pipeline-stages-list").get(0);if(!l)return;const n=l.querySelector(".stage-empty-placeholder");n&&n.remove();const a=["$match","$sort"].includes(t.type),s=document.createElement("div");s.className="card mb-2 stage-card",s.dataset.stageType=t.type,a&&(s.dataset.guided="true");const r=document.createElement("div");r.className="card-header",r.style.cssText="display:flex;align-items:center;gap:.5rem;";const i=document.createElement("code");i.textContent=t.type,i.style.cssText="flex:1;font-size:.85rem;";const o=document.createElement("button");o.type="button",o.className="btn btn-sm btn-danger";const c=document.createElement("span");c.setAttribute("data-icon","trash-2"),o.appendChild(c),o.addEventListener("click",()=>{s.remove(),l.querySelector(".stage-card")||l.appendChild(G())}),r.appendChild(i),r.appendChild(o);const d=document.createElement("div");if(d.className="card-body",a){const u=document.createElement("div");u.className="stage-guided",d.appendChild(u),t.type==="$match"&&W(u,t.config),t.type==="$sort"&&V(u,t.config)}else{const u=document.createElement("label");u.className="form-label",u.textContent="Stage Config (JSON)";const m=document.createElement("small");m.className="text-muted",m.style.cssText="display:block;margin-bottom:.4rem;",m.textContent=`Enter the inner config for ${t.type} \u2014 e.g. for $lookup: { from, localField, foreignField, as }. Do not wrap in { "${t.type}": ... }.`;const p=document.createElement("textarea");p.className="form-input stage-config",p.rows=5,p.style.cssText="font-family:monospace;font-size:.8rem;resize:vertical;",p.placeholder="{}",p.value=Object.keys(t.config||{}).length?JSON.stringify(t.config,null,2):"",d.appendChild(u),d.appendChild(m),d.appendChild(p)}s.appendChild(r),s.appendChild(d),l.appendChild(s),Domma.icons.scan(s)}function G(){const e=document.createElement("p");return e.className="text-muted stage-empty-placeholder",e.textContent="No stages yet. Add a stage to filter, join, or transform your data.",e.style.cssText="text-align:center;padding:2rem 0;",e}function K(e){const t=[];return e.find(".stage-card").each(function(){const l=this.dataset.stageType,n=this.querySelector(".stage-guided");let a;if(n)l==="$match"&&(a=U(n)),l==="$sort"&&(a=J(n));else{const s=this.querySelector(".stage-config")?.value?.trim()||"{}";try{a=JSON.parse(s)}catch{throw new Error(`Invalid JSON in ${l} stage config`)}}t.push({type:l,config:a})}),t}function A(e,t){const l=e.find("#view-columns-builder").get(0);if(!l)return;for(;l.firstChild;)l.removeChild(l.firstChild);const n=t??(f||[]).map(i=>({key:`data.${i.name}`,label:i.label||i.name})),a=(i="",o="")=>{const c=document.createElement("div");c.className="col-row",c.style.cssText="display:flex;gap:.4rem;align-items:center;margin-bottom:.4rem;";const d=f?.length?v([{value:"",label:"\u2014 field \u2014"},...g()],i):N("data.fieldName",i);d.className+=" col-key";const u=N("Display label",o);u.className+=" col-label",u.style.flex="1",f?.length&&d.tagName==="SELECT"&&d.addEventListener("change",()=>{if(!u.value){const w=(f||[]).find(L=>`data.${L.name}`===d.value);w&&(u.value=w.label||w.name)}});const m=document.createElement("button");m.type="button",m.className="btn btn-sm btn-ghost",m.title="Remove column";const p=document.createElement("span");p.setAttribute("data-icon","x"),m.appendChild(p),m.addEventListener("click",()=>{c.remove(),Domma.icons.scan(l)}),c.appendChild(d),c.appendChild(u),c.appendChild(m),l.insertBefore(c,l.lastChild),Domma.icons.scan(c)};n.forEach(i=>a(i.key,i.label));const s=document.createElement("button");s.type="button",s.className="btn btn-ghost btn-sm";const r=document.createElement("span");r.setAttribute("data-icon","plus"),s.appendChild(r),s.appendChild(document.createTextNode(" Add Column")),s.addEventListener("click",()=>{a(),Domma.icons.scan(l)}),l.appendChild(s),Domma.icons.scan(l)}function P(e){const t=[];return e.find(".col-row").each(function(){const l=this.querySelector(".col-key")?.value?.trim(),n=this.querySelector(".col-label")?.value?.trim();l&&t.push({key:l,label:n||l})}),t}function Y(e){const t=e.find("#view-display-mode").get(0);t&&(t.addEventListener("change",()=>S(e)),S(e))}function S(e){const t=e.find("#view-display-mode").val()||"table",l=e.find("#view-columns-section").get(0),n=e.find("#view-block-section").get(0);l&&(l.style.display=t==="table"?"":"none"),n&&(n.style.display=t==="block"?"":"none")}function H(e){const t=e.find("#view-rowlevel-enabled").get(0),l=e.find("#view-rowlevel-config").get(0),n=e.find("#view-rowlevel-mode").get(0),a=e.find("#view-rowlevel-field-group").get(0);t&&(t.addEventListener("change",()=>{l&&(l.style.display=t.checked?"flex":"none")}),n&&n.addEventListener("change",()=>{a&&(a.style.display=n.value==="field"?"":"none")}))}async function Q(e){const t=e.find("#view-title").val().trim();if(!t){E.toast("Title is required.",{type:"warning"});return}const l=e.find("#view-source").val();if(!l){E.toast("Source collection is required (Source tab).",{type:"warning"});return}let n;try{n=K(e)}catch(d){E.toast(d.message,{type:"error"});return}const a=P(e),s=[];e.find(".view-role-cb:checked").each(function(){s.push(this.value)});const r=e.find("#view-rowlevel-enabled").is(":checked");let i=null;if(r){const d=e.find("#view-rowlevel-mode").val()||"owner",u=e.find("#view-rowlevel-userkey").val()||"id";if(i={mode:d,userKey:u},d==="field"){const m=e.find("#view-rowlevel-field").val().trim();if(!m){E.toast("Field name is required for Field Match mode.",{type:"warning"});return}i.field=m}}const o={title:t,slug:e.find("#view-slug").val().trim()||void 0,description:e.find("#view-description").val().trim(),connection:e.find("#view-connection").val()||"default",pipeline:{source:l,stages:n},display:{mode:e.find("#view-display-mode").val()||"table",columns:a,pageSize:parseInt(e.find("#view-page-size").val(),10)||25,block:e.find("#view-block-name").val()||""},access:{roles:s,public:e.find("#view-public").is(":checked"),rowLevel:i}},c=e.find("#save-view-btn").get(0);c&&(c.disabled=!0);try{if(b)await h.views.update(b,o),E.toast("View updated.",{type:"success"});else{const d=await h.views.create(o);E.toast("View created.",{type:"success"}),R.navigate(`/views/edit/${d.slug}`)}}catch(d){E.toast(d.message||"Failed to save view.",{type:"error"})}finally{c&&(c.disabled=!1)}}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{api as m}from"../api.js";let l=null,r=1,c=0;export const viewPreviewView={templateUrl:"/admin/js/templates/view-preview.html",async onMount(e){l=null,r=1,c=0;const i=window.location.hash.match(/\/views\/([^/?#]+)\/preview/),s=i?i[1]:null;if(!s){E.toast("No view selected.",{type:"error"}),R.navigate("/views");return}try{if(l=await m.views.get(s),!l){E.toast("View not found.",{type:"error"}),R.navigate("/views");return}}catch(t){E.toast(t.message||"Failed to load view.",{type:"error"});return}e.find("#preview-title").text(l.title),e.find("#preview-edit-btn").attr("href",`#/views/edit/${l.slug}`),e.find("#preview-source").text(l.pipeline?.source||"\u2014"),e.find("#preview-connection").text(l.connection||"default"),e.find("#preview-mode").text(l.display?.mode||"table"),e.find("#preview-meta-card").show(),e.find("#preview-run-btn").off("click").on("click",async()=>{r=1,await v(e)}),e.find("#preview-prev").off("click").on("click",async()=>{r>1&&(r--,await v(e))}),e.find("#preview-next").off("click").on("click",async()=>{const t=l.display?.pageSize||25;r*t<c&&(r++,await v(e))}),Domma.icons.scan()}};async function v(e){const n=l.display?.pageSize||25,i=e.find("#preview-run-btn").get(0);i&&(i.disabled=!0,i.textContent="Running\u2026"),e.find("#preview-placeholder").hide(),e.find("#preview-table").hide(),e.find("#preview-list").hide(),e.find("#preview-empty").hide();try{const s=await m.views.execute(l.slug,{page:r,limit:n});c=s.total||0;const t=Math.max(1,Math.ceil(c/n));if(e.find("#preview-count").text(`${c} result${c!==1?"s":""}`),e.find("#preview-page-label").text(`Page ${r} of ${t}`),e.find("#preview-prev").prop("disabled",r<=1),e.find("#preview-next").prop("disabled",r>=t),e.find("#preview-pagination").show(),!s.results||s.results.length===0){e.find("#preview-empty").show();return}(l.display?.mode||"table")==="list"?g(s.results,e):y(s.results,e)}catch(s){E.toast(s.message||"Execution failed.",{type:"error"})}finally{i&&(i.disabled=!1,i.textContent="Run")}}function y(e,n){const i=l.display?.columns||[],s=i.length?i:Object.keys(u(e[0])).slice(0,6).map(t=>({key:t,label:t}));T.create("#preview-table",{data:e,columns:s.map(t=>({key:t.key,title:t.label||t.key,render:(d,f)=>{const w=t.key.split(".");let a=f;for(const h of w)a=a?.[h];const o=a!=null?String(a):"",p=document.createElement("span");return p.title=o,p.textContent=o.length>80?o.slice(0,80)+"\u2026":o,p.outerHTML}})),emptyMessage:"No results."}),n.find("#preview-table").show(),Domma.icons.scan()}function g(e,n){const i=n.find("#preview-list").get(0);if(i){for(;i.firstChild;)i.removeChild(i.firstChild);e.forEach(s=>{const t=document.createElement("div");t.style.cssText="padding:.75rem;border-bottom:1px solid var(--dm-border,#333);";const d=u(s);Object.entries(d).slice(0,6).forEach(([f,w])=>{const a=document.createElement("div");a.style.cssText="display:flex;gap:.5rem;font-size:.875rem;";const o=document.createElement("strong");o.textContent=f+": ",o.style.cssText="color:var(--dm-text-muted,#888);min-width:120px;flex-shrink:0;";const p=document.createElement("span");p.textContent=String(w??""),a.appendChild(o),a.appendChild(p),t.appendChild(a)}),i.appendChild(t)}),n.find("#preview-list").show()}}function u(e,n="",i={}){for(const[s,t]of Object.entries(e||{})){const d=n?`${n}.${s}`:s;t&&typeof t=="object"&&!Array.isArray(t)&&Object.keys(t).length<8?u(t,d,i):i[d]=t}return i}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{api as r}from"../api.js";function i(s){return String(s).replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""")}export const viewsListView={templateUrl:"/admin/js/templates/views-list.html",async onMount(s){await l(s),s.find("#create-view-btn").off("click").on("click",()=>{R.navigate("/views/new")}),Domma.icons.scan()}};async function l(s){let c=[];try{c=await r.views.list(),s.find("#views-pro-notice").hide()}catch(e){e.message?.includes("MongoDB")||e.message?.includes("pro mode")||e.message?.includes("connection")?s.find("#views-pro-notice").show():E.toast("Could not load views.",{type:"error"})}T.create("#views-table",{data:c,columns:[{key:"title",title:"Title",render:(e,t)=>{const n=document.createElement("a");return n.href=`#/views/${i(t.slug)}/preview`,n.textContent=e,n.style.fontWeight="600",n.outerHTML}},{key:"slug",title:"Slug",render:e=>`<code>${i(e)}</code>`},{key:"pipeline",title:"Source",render:e=>`<code>${i(e?.source||"\u2014")}</code>`},{key:"display",title:"Mode",render:e=>i(e?.mode||"table")},{key:"access",title:"Roles",render:e=>(e?.roles||[]).map(t=>`<span class="badge badge-secondary">${i(t)}</span>`).join(" ")},{key:"slug",title:"Actions",render:e=>{const t=document.createElement("div");t.style.cssText="display:flex;gap:.4rem;justify-content:flex-end;";const n=document.createElement("a");n.href=`#/views/${i(e)}/preview`,n.className="btn btn-sm btn-ghost",n.textContent="Preview";const a=document.createElement("a");a.href=`#/views/edit/${i(e)}`,a.className="btn btn-sm btn-primary",a.textContent="Edit";const o=document.createElement("button");return o.className="btn btn-sm btn-danger js-delete-view",o.dataset.slug=e,o.textContent="Delete",t.appendChild(n),t.appendChild(a),t.appendChild(o),t.outerHTML}}],emptyMessage:'No views yet. Click "New View" to create your first view.'}),document.querySelectorAll(".js-delete-view").forEach(e=>{e.addEventListener("click",async()=>{const t=e.dataset.slug;if(await E.confirm(`Delete view "${t}"? This cannot be undone.`))try{await r.views.delete(t),E.toast("View deleted.",{type:"success"}),await l(s)}catch{E.toast("Failed to delete view.",{type:"error"})}})}),Domma.icons.scan()}
|
package/bin/cli.js
CHANGED
|
@@ -366,5 +366,5 @@ if (noInstall) console.log(` npm install`);
|
|
|
366
366
|
if (noSetup && !noInstall) console.log(` npm run setup`);
|
|
367
367
|
console.log(` npm run dev`);
|
|
368
368
|
console.log('');
|
|
369
|
-
console.log(` Then open: http://localhost:
|
|
369
|
+
console.log(` Then open: http://localhost:4096/admin`);
|
|
370
370
|
console.log('');
|
package/config/auth.json
CHANGED
|
@@ -1,21 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"accessTokenExpiry": "15m",
|
|
3
3
|
"refreshTokenExpiry": "7d",
|
|
4
|
-
|
|
5
|
-
"
|
|
6
|
-
"admin": { "label": "Admin", "level": 0 },
|
|
7
|
-
"manager": { "label": "Manager", "level": 1 },
|
|
8
|
-
"editor": { "label": "Editor", "level": 2 },
|
|
9
|
-
"subscriber": { "label": "Subscriber", "level": 3 }
|
|
10
|
-
},
|
|
11
|
-
"permissions": {
|
|
12
|
-
"pages": ["admin", "manager", "editor"],
|
|
13
|
-
"settings": ["admin", "manager"],
|
|
14
|
-
"navigation": ["admin", "manager"],
|
|
15
|
-
"layouts": ["admin", "manager"],
|
|
16
|
-
"media": ["admin", "manager", "editor"],
|
|
17
|
-
"users": ["admin", "manager"],
|
|
18
|
-
"plugins": ["admin"],
|
|
19
|
-
"collections": ["admin", "manager"]
|
|
20
|
-
}
|
|
4
|
+
"resetTokenExpiry": "1h",
|
|
5
|
+
"bcryptRounds": 10
|
|
21
6
|
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
{
|
|
2
|
+
"default": {
|
|
3
|
+
"type": "mongodb",
|
|
4
|
+
"uri": "mongodb://localhost:27017",
|
|
5
|
+
"database": "domma_cms",
|
|
6
|
+
"options": {}
|
|
7
|
+
},
|
|
8
|
+
"_comment": "Copy this file to connections.json and update with your MongoDB connection details. Each key is a named connection. Collections reference connections by name in their schema.json storage config. The 'default' connection is used when no connection name is specified."
|
|
9
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
{
|
|
2
|
+
"default": {
|
|
3
|
+
"type": "mongodb",
|
|
4
|
+
"uri": "mongodb://localhost:27017",
|
|
5
|
+
"database": "domma_cms",
|
|
6
|
+
"options": {}
|
|
7
|
+
},
|
|
8
|
+
"_comment": "Copy this file to connections.json and update with your MongoDB connection details. Each key is a named connection. Collections reference connections by name in their schema.json storage config. The 'default' connection is used when no connection name is specified."
|
|
9
|
+
}
|
package/config/navigation.json
CHANGED
|
@@ -25,6 +25,11 @@
|
|
|
25
25
|
"url": "/contact",
|
|
26
26
|
"icon": "mail"
|
|
27
27
|
},
|
|
28
|
+
{
|
|
29
|
+
"text": "Feedback",
|
|
30
|
+
"url": "/feedback",
|
|
31
|
+
"icon": "message-circle"
|
|
32
|
+
},
|
|
28
33
|
{
|
|
29
34
|
"text": "Resources",
|
|
30
35
|
"url": "/resources",
|
|
@@ -64,6 +69,16 @@
|
|
|
64
69
|
"text": "Interactive",
|
|
65
70
|
"url": "/resources/interactive",
|
|
66
71
|
"icon": "mouse-pointer"
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
"text": "Components",
|
|
75
|
+
"url": "/resources/components",
|
|
76
|
+
"icon": "layers"
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
"text": "Dependencies",
|
|
80
|
+
"url": "/resources/dependencies",
|
|
81
|
+
"icon": "package"
|
|
67
82
|
}
|
|
68
83
|
]
|
|
69
84
|
}
|
package/config/plugins.json
CHANGED
|
@@ -1,29 +1,19 @@
|
|
|
1
|
-
{
|
|
2
|
-
"example-analytics": {
|
|
3
|
-
"enabled": true,
|
|
4
|
-
"settings": {}
|
|
5
|
-
},
|
|
6
|
-
"
|
|
7
|
-
"enabled": true,
|
|
8
|
-
"settings": {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
"
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
"domma-effects": {
|
|
21
|
-
"enabled": true,
|
|
22
|
-
"settings": {
|
|
23
|
-
"respectMotion": false,
|
|
24
|
-
"defaultDuration": 600,
|
|
25
|
-
"defaultAnimation": "fade",
|
|
26
|
-
"defaultThreshold": 0.1
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"example-analytics": {
|
|
3
|
+
"enabled": true,
|
|
4
|
+
"settings": {}
|
|
5
|
+
},
|
|
6
|
+
"theme-roller": {
|
|
7
|
+
"enabled": true,
|
|
8
|
+
"settings": {}
|
|
9
|
+
},
|
|
10
|
+
"domma-effects": {
|
|
11
|
+
"enabled": true,
|
|
12
|
+
"settings": {
|
|
13
|
+
"respectMotion": false,
|
|
14
|
+
"defaultDuration": 600,
|
|
15
|
+
"defaultAnimation": "fade",
|
|
16
|
+
"defaultThreshold": 0.1
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
package/config/server.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
{
|
|
2
|
-
|
|
3
|
-
"host": "0.0.0.0",
|
|
4
|
-
"cors": { "origin":
|
|
5
|
-
"uploads": { "maxFileSize": 10485760 }
|
|
6
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"port": 4096,
|
|
3
|
+
"host": "0.0.0.0",
|
|
4
|
+
"cors": { "origin": false },
|
|
5
|
+
"uploads": { "maxFileSize": 10485760 }
|
|
6
|
+
}
|
package/config/site.json
CHANGED
|
@@ -3,8 +3,11 @@
|
|
|
3
3
|
"tagline": "My Dead Good Site",
|
|
4
4
|
"fontFamily": "Roboto",
|
|
5
5
|
"fontSize": 16,
|
|
6
|
-
"theme": "
|
|
6
|
+
"theme": "silver-dark",
|
|
7
7
|
"adminTheme": "charcoal-dark",
|
|
8
|
+
"layoutOptions": {
|
|
9
|
+
"spacerSize": 40
|
|
10
|
+
},
|
|
8
11
|
"seo": {
|
|
9
12
|
"defaultTitle": "My Boss Site",
|
|
10
13
|
"titleSeparator": " | ",
|
|
@@ -41,16 +44,17 @@
|
|
|
41
44
|
"user": "",
|
|
42
45
|
"pass": "",
|
|
43
46
|
"secure": false,
|
|
44
|
-
"fromAddress": "",
|
|
45
|
-
"fromName": ""
|
|
47
|
+
"fromAddress": "noreply@dwit.uk",
|
|
48
|
+
"fromName": "My Big Boss Website"
|
|
46
49
|
},
|
|
47
50
|
"backToTop": {
|
|
48
51
|
"enabled": true,
|
|
49
|
-
"scrollThreshold":
|
|
52
|
+
"scrollThreshold": 180,
|
|
50
53
|
"position": "bottom-right",
|
|
54
|
+
"offset": 16,
|
|
55
|
+
"bottomOffset": 16,
|
|
51
56
|
"label": "",
|
|
52
|
-
"smooth": true
|
|
53
|
-
"offset": 48
|
|
57
|
+
"smooth": true
|
|
54
58
|
},
|
|
55
59
|
"cookieConsent": {
|
|
56
60
|
"enabled": true,
|
|
@@ -70,5 +74,12 @@
|
|
|
70
74
|
"showAnalytics": true,
|
|
71
75
|
"showMarketing": true,
|
|
72
76
|
"consentVersion": "1.0"
|
|
77
|
+
},
|
|
78
|
+
"breadcrumbs": {
|
|
79
|
+
"enabled": true,
|
|
80
|
+
"homeLabel": "Home",
|
|
81
|
+
"position": "TL",
|
|
82
|
+
"offsetX": 24,
|
|
83
|
+
"offsetY": 64
|
|
73
84
|
}
|
|
74
85
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "domma-cms",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "File-based CMS powered by Domma and Fastify. Run npx domma-cms my-site to create a new project.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "server/server.js",
|
|
@@ -10,25 +10,34 @@
|
|
|
10
10
|
"files": [
|
|
11
11
|
"bin/",
|
|
12
12
|
"server/",
|
|
13
|
-
"admin/",
|
|
14
|
-
"
|
|
13
|
+
"admin/js/",
|
|
14
|
+
"admin/css/",
|
|
15
|
+
"admin/index.html",
|
|
16
|
+
"admin/dist/domma/domma-tools.css",
|
|
17
|
+
"admin/dist/domma/domma-tools.min.js",
|
|
18
|
+
"public/js/",
|
|
19
|
+
"public/css/",
|
|
15
20
|
"config/",
|
|
21
|
+
"!config/connections.json",
|
|
16
22
|
"plugins/",
|
|
17
|
-
"scripts/"
|
|
23
|
+
"scripts/",
|
|
24
|
+
"docs/",
|
|
25
|
+
"CHANGELOG.md"
|
|
18
26
|
],
|
|
19
27
|
"scripts": {
|
|
20
28
|
"build": "node scripts/build.js",
|
|
21
|
-
"start": "
|
|
22
|
-
"
|
|
29
|
+
"start": "node server/server.js",
|
|
30
|
+
"start:cluster": "pm2 start server/server.js -i max --name domma-cms",
|
|
31
|
+
"dev": "PORT=4096 node --watch server/server.js",
|
|
23
32
|
"prod": "node server/server.js",
|
|
24
33
|
"setup": "node scripts/setup.js",
|
|
25
34
|
"reset": "node scripts/reset.js",
|
|
26
35
|
"seed": "node scripts/seed.js",
|
|
27
36
|
"fresh": "node scripts/fresh.js",
|
|
28
|
-
"
|
|
37
|
+
"pro": "node scripts/pro.js"
|
|
29
38
|
},
|
|
30
39
|
"engines": {
|
|
31
|
-
"node": ">=18.
|
|
40
|
+
"node": ">=18.17.0"
|
|
32
41
|
},
|
|
33
42
|
"keywords": [
|
|
34
43
|
"cms",
|
|
@@ -51,13 +60,15 @@
|
|
|
51
60
|
},
|
|
52
61
|
"dependencies": {
|
|
53
62
|
"@fastify/cors": "^11.2.0",
|
|
63
|
+
"@fastify/helmet": "^13.0.2",
|
|
54
64
|
"@fastify/jwt": "^10.0.0",
|
|
55
65
|
"@fastify/multipart": "^9.3.0",
|
|
66
|
+
"@fastify/rate-limit": "^10.3.0",
|
|
56
67
|
"@fastify/static": "^8.1.0",
|
|
57
68
|
"bcryptjs": "^3.0.3",
|
|
58
|
-
"domma-js": "^0.19.
|
|
69
|
+
"domma-js": "^0.19.4",
|
|
59
70
|
"dotenv": "^17.2.3",
|
|
60
|
-
"fastify": "
|
|
71
|
+
"fastify": "5.8.1",
|
|
61
72
|
"gray-matter": "^4.0.3",
|
|
62
73
|
"marked": "^15.0.0",
|
|
63
74
|
"nodemailer": "^8.0.1",
|
|
@@ -65,6 +76,9 @@
|
|
|
65
76
|
"sharp": "^0.34.5",
|
|
66
77
|
"uuid": "^13.0.0"
|
|
67
78
|
},
|
|
79
|
+
"optionalDependencies": {
|
|
80
|
+
"mongodb": "^6.21.0"
|
|
81
|
+
},
|
|
68
82
|
"devDependencies": {
|
|
69
83
|
"esbuild": "^0.27.3"
|
|
70
84
|
}
|
|
@@ -1,30 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
* Canvas Management Utilities
|
|
3
|
-
* Handles canvas creation, resizing, and context management
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
export class CanvasManager {
|
|
7
|
-
constructor(options = {}) {
|
|
8
|
-
this.canvasId = options.canvasId || 'celebrations-canvas';
|
|
9
|
-
this.zIndex = options.zIndex || 999;
|
|
10
|
-
this.canvas = null;
|
|
11
|
-
this.ctx = null;
|
|
12
|
-
this.resizeTimeout = null;
|
|
13
|
-
this.resizeCallback = options.onResize || null;
|
|
14
|
-
|
|
15
|
-
// Bind methods
|
|
16
|
-
this._handleResize = this._handleResize.bind(this);
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Create and append canvas to DOM
|
|
21
|
-
*/
|
|
22
|
-
create() {
|
|
23
|
-
if (this.canvas) return; // Already created
|
|
24
|
-
|
|
25
|
-
this.canvas = document.createElement('canvas');
|
|
26
|
-
this.canvas.id = this.canvasId;
|
|
27
|
-
this.canvas.style.cssText = `
|
|
1
|
+
export class CanvasManager{constructor(e={}){this.canvasId=e.canvasId||"celebrations-canvas",this.zIndex=e.zIndex||999,this.canvas=null,this.ctx=null,this.resizeTimeout=null,this.resizeCallback=e.onResize||null,this._handleResize=this._handleResize.bind(this)}create(){this.canvas||(this.canvas=document.createElement("canvas"),this.canvas.id=this.canvasId,this.canvas.style.cssText=`
|
|
28
2
|
position: fixed;
|
|
29
3
|
top: 0;
|
|
30
4
|
left: 0;
|
|
@@ -32,80 +6,4 @@ export class CanvasManager {
|
|
|
32
6
|
height: 100vh;
|
|
33
7
|
pointer-events: none;
|
|
34
8
|
z-index: ${this.zIndex};
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
this.ctx = this.canvas.getContext('2d', { alpha: true });
|
|
38
|
-
this.resize();
|
|
39
|
-
document.body.appendChild(this.canvas);
|
|
40
|
-
|
|
41
|
-
// Bind resize listener
|
|
42
|
-
window.addEventListener('resize', this._handleResize);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* Resize canvas to match window dimensions
|
|
47
|
-
*/
|
|
48
|
-
resize() {
|
|
49
|
-
if (!this.canvas) return;
|
|
50
|
-
|
|
51
|
-
this.canvas.width = window.innerWidth;
|
|
52
|
-
this.canvas.height = window.innerHeight;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* Clear entire canvas
|
|
57
|
-
*/
|
|
58
|
-
clear() {
|
|
59
|
-
if (!this.ctx) return;
|
|
60
|
-
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* Get canvas dimensions
|
|
65
|
-
*/
|
|
66
|
-
getDimensions() {
|
|
67
|
-
return {
|
|
68
|
-
width: this.canvas?.width || 0,
|
|
69
|
-
height: this.canvas?.height || 0
|
|
70
|
-
};
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* Check if mobile device
|
|
75
|
-
*/
|
|
76
|
-
isMobile() {
|
|
77
|
-
return window.innerWidth < 768;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* Remove canvas from DOM and cleanup
|
|
82
|
-
*/
|
|
83
|
-
destroy() {
|
|
84
|
-
window.removeEventListener('resize', this._handleResize);
|
|
85
|
-
|
|
86
|
-
if (this.canvas && this.canvas.parentNode) {
|
|
87
|
-
this.canvas.parentNode.removeChild(this.canvas);
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
this.canvas = null;
|
|
91
|
-
this.ctx = null;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
/**
|
|
95
|
-
* Handle window resize with debouncing
|
|
96
|
-
* @private
|
|
97
|
-
*/
|
|
98
|
-
_handleResize() {
|
|
99
|
-
if (this.resizeTimeout) {
|
|
100
|
-
clearTimeout(this.resizeTimeout);
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
this.resizeTimeout = setTimeout(() => {
|
|
104
|
-
this.resize();
|
|
105
|
-
|
|
106
|
-
if (this.resizeCallback) {
|
|
107
|
-
this.resizeCallback();
|
|
108
|
-
}
|
|
109
|
-
}, 250);
|
|
110
|
-
}
|
|
111
|
-
}
|
|
9
|
+
`,this.ctx=this.canvas.getContext("2d",{alpha:!0}),this.resize(),document.body.appendChild(this.canvas),window.addEventListener("resize",this._handleResize))}resize(){this.canvas&&(this.canvas.width=window.innerWidth,this.canvas.height=window.innerHeight)}clear(){this.ctx&&this.ctx.clearRect(0,0,this.canvas.width,this.canvas.height)}getDimensions(){return{width:this.canvas?.width||0,height:this.canvas?.height||0}}isMobile(){return window.innerWidth<768}destroy(){window.removeEventListener("resize",this._handleResize),this.canvas&&this.canvas.parentNode&&this.canvas.parentNode.removeChild(this.canvas),this.canvas=null,this.ctx=null}_handleResize(){this.resizeTimeout&&clearTimeout(this.resizeTimeout),this.resizeTimeout=setTimeout(()=>{this.resize(),this.resizeCallback&&this.resizeCallback()},250)}}
|