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
|
@@ -1,153 +1 @@
|
|
|
1
|
-
|
|
2
|
-
* Collections List View
|
|
3
|
-
* Shows all collections with field/entry counts and action buttons.
|
|
4
|
-
*/
|
|
5
|
-
import {api} from '../api.js';
|
|
6
|
-
|
|
7
|
-
function esc(str) {
|
|
8
|
-
return String(str)
|
|
9
|
-
.replace(/&/g, '&').replace(/</g, '<')
|
|
10
|
-
.replace(/>/g, '>').replace(/"/g, '"');
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export const collectionsView = {
|
|
14
|
-
templateUrl: '/admin/js/templates/collections.html',
|
|
15
|
-
|
|
16
|
-
async onMount($container) {
|
|
17
|
-
await loadCollections($container);
|
|
18
|
-
|
|
19
|
-
$container.find('#create-collection-btn').off('click').on('click', () => {
|
|
20
|
-
const modal = E.modal({ title: 'New Collection', size: 'sm' });
|
|
21
|
-
|
|
22
|
-
const wrapper = document.createElement('div');
|
|
23
|
-
wrapper.style.cssText = 'padding:.25rem 0 .5rem;';
|
|
24
|
-
|
|
25
|
-
const formContainer = document.createElement('div');
|
|
26
|
-
F.create(
|
|
27
|
-
{ title: { type: 'string', label: 'Collection Title', placeholder: 'e.g. Products, Blog Posts…', required: true } },
|
|
28
|
-
{},
|
|
29
|
-
{ showSubmitButton: false }
|
|
30
|
-
).renderTo(formContainer);
|
|
31
|
-
wrapper.appendChild(formContainer);
|
|
32
|
-
|
|
33
|
-
const btnWrap = document.createElement('div');
|
|
34
|
-
btnWrap.style.cssText = 'display:flex;justify-content:flex-end;gap:.5rem;margin-top:.75rem;';
|
|
35
|
-
const cancelBtn = document.createElement('button');
|
|
36
|
-
cancelBtn.className = 'btn btn-ghost';
|
|
37
|
-
cancelBtn.textContent = 'Cancel';
|
|
38
|
-
const createBtn = document.createElement('button');
|
|
39
|
-
createBtn.className = 'btn btn-primary';
|
|
40
|
-
createBtn.textContent = 'Create';
|
|
41
|
-
btnWrap.appendChild(cancelBtn);
|
|
42
|
-
btnWrap.appendChild(createBtn);
|
|
43
|
-
wrapper.appendChild(btnWrap);
|
|
44
|
-
|
|
45
|
-
modal.element.appendChild(wrapper);
|
|
46
|
-
modal.open();
|
|
47
|
-
|
|
48
|
-
const titleInput = formContainer.querySelector('input[name="title"]');
|
|
49
|
-
setTimeout(() => titleInput?.focus(), 50);
|
|
50
|
-
|
|
51
|
-
async function doCreate() {
|
|
52
|
-
const title = titleInput?.value.trim();
|
|
53
|
-
if (!title) return;
|
|
54
|
-
try {
|
|
55
|
-
const collection = await api.collections.create({ title });
|
|
56
|
-
modal.close();
|
|
57
|
-
R.navigate(`/collections/edit/${collection.slug}`);
|
|
58
|
-
} catch (err) {
|
|
59
|
-
E.toast(err.message || 'Failed to create collection.', { type: 'error' });
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
cancelBtn.addEventListener('click', () => modal.close());
|
|
64
|
-
createBtn.addEventListener('click', doCreate);
|
|
65
|
-
titleInput?.addEventListener('keydown', (e) => { if (e.key === 'Enter') doCreate(); });
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
Domma.icons.scan();
|
|
69
|
-
}
|
|
70
|
-
};
|
|
71
|
-
|
|
72
|
-
async function loadCollections($container) {
|
|
73
|
-
let collections = [];
|
|
74
|
-
try {
|
|
75
|
-
collections = await api.collections.list();
|
|
76
|
-
} catch {
|
|
77
|
-
E.toast('Could not load collections.', { type: 'error' });
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
T.create('#collections-table', {
|
|
81
|
-
data: collections,
|
|
82
|
-
columns: [
|
|
83
|
-
{
|
|
84
|
-
key: 'title',
|
|
85
|
-
title: 'Title',
|
|
86
|
-
render: (val, row) => {
|
|
87
|
-
const a = document.createElement('a');
|
|
88
|
-
a.href = `#/collections/${esc(row.slug)}/entries`;
|
|
89
|
-
a.textContent = val;
|
|
90
|
-
a.style.fontWeight = '600';
|
|
91
|
-
return a.outerHTML;
|
|
92
|
-
}
|
|
93
|
-
},
|
|
94
|
-
{
|
|
95
|
-
key: 'slug',
|
|
96
|
-
title: 'Slug',
|
|
97
|
-
render: val => {
|
|
98
|
-
const code = document.createElement('code');
|
|
99
|
-
code.textContent = val;
|
|
100
|
-
return code.outerHTML;
|
|
101
|
-
}
|
|
102
|
-
},
|
|
103
|
-
{key: 'fields', title: 'Field Count', render: val => String(val?.length ?? 0)},
|
|
104
|
-
{key: 'entryCount', title: 'Entry Count', render: val => String(val ?? 0)},
|
|
105
|
-
{
|
|
106
|
-
key: 'slug',
|
|
107
|
-
title: 'Actions',
|
|
108
|
-
render: (val) => {
|
|
109
|
-
const wrap = document.createElement('div');
|
|
110
|
-
wrap.style.cssText = 'display:flex;gap:.4rem;justify-content:flex-end;';
|
|
111
|
-
|
|
112
|
-
const editBtn = document.createElement('a');
|
|
113
|
-
editBtn.href = `#/collections/edit/${esc(val)}`;
|
|
114
|
-
editBtn.className = 'btn btn-sm btn-ghost';
|
|
115
|
-
editBtn.textContent = 'Edit Schema';
|
|
116
|
-
|
|
117
|
-
const entriesBtn = document.createElement('a');
|
|
118
|
-
entriesBtn.href = `#/collections/${esc(val)}/entries`;
|
|
119
|
-
entriesBtn.className = 'btn btn-sm btn-ghost';
|
|
120
|
-
entriesBtn.textContent = 'Entries';
|
|
121
|
-
|
|
122
|
-
const delBtn = document.createElement('button');
|
|
123
|
-
delBtn.className = 'btn btn-sm btn-danger js-delete-collection';
|
|
124
|
-
delBtn.dataset.slug = val;
|
|
125
|
-
delBtn.textContent = 'Delete';
|
|
126
|
-
|
|
127
|
-
wrap.appendChild(editBtn);
|
|
128
|
-
wrap.appendChild(entriesBtn);
|
|
129
|
-
wrap.appendChild(delBtn);
|
|
130
|
-
return wrap.outerHTML;
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
],
|
|
134
|
-
emptyMessage: 'No collections yet. Click "New Collection" to get started.'
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
document.querySelectorAll('.js-delete-collection').forEach(btn => {
|
|
138
|
-
btn.addEventListener('click', async () => {
|
|
139
|
-
const slug = btn.dataset.slug;
|
|
140
|
-
const confirmed = await E.confirm(`Delete collection "${slug}" and all its data? This cannot be undone.`);
|
|
141
|
-
if (!confirmed) return;
|
|
142
|
-
try {
|
|
143
|
-
await api.collections.delete(slug);
|
|
144
|
-
E.toast('Collection deleted.', { type: 'success' });
|
|
145
|
-
await loadCollections($container);
|
|
146
|
-
} catch {
|
|
147
|
-
E.toast('Failed to delete collection.', { type: 'error' });
|
|
148
|
-
}
|
|
149
|
-
});
|
|
150
|
-
});
|
|
151
|
-
|
|
152
|
-
Domma.icons.scan();
|
|
153
|
-
}
|
|
1
|
+
import{api as h}from"../api.js";function b(e){return String(e).replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""")}let i={};export const collectionsView={templateUrl:"/admin/js/templates/collections.html",async onMount(e){const a=E.loader(e.get(0),{type:"dots"});E.tabs(e.find("#collections-tabs").get(0));const o=e.find("#collections-tabs").get(0)?.querySelector(".tab-list");if(o){const t=e.find("#collections-header-actions").get(0);Array.from(o.querySelectorAll(".tab-item")).forEach((n,c)=>{n.addEventListener("click",()=>{t&&(t.style.display=c===0?"":"none")})})}await Promise.all([j(e),q(e)]),a.destroy(),e.find("#create-collection-btn").off("click").on("click",()=>{const t=E.modal({title:"New Collection",size:"sm"}),n=document.createElement("div");n.style.cssText="padding:.25rem 0 .5rem;";const c=document.createElement("div");F.create({title:{type:"string",label:"Collection Title",placeholder:"e.g. Products, Blog Posts\u2026",required:!0}},{},{showSubmitButton:!1}).renderTo(c),n.appendChild(c);const d=document.createElement("div");d.style.cssText="display:flex;justify-content:flex-end;gap:.5rem;margin-top:.75rem;";const s=document.createElement("button");s.className="btn btn-ghost",s.textContent="Cancel";const l=document.createElement("button");l.className="btn btn-primary",l.textContent="Create",d.appendChild(s),d.appendChild(l),n.appendChild(d),t.element.appendChild(n),t.open();const r=c.querySelector('input[name="title"]');setTimeout(()=>r?.focus(),50);async function m(){const p=r?.value.trim();if(p)try{const u=await h.collections.create({title:p});t.close(),R.navigate(`/collections/edit/${u.slug}`)}catch(u){E.toast(u.message||"Failed to create collection.",{type:"error"})}}s.addEventListener("click",()=>t.close()),l.addEventListener("click",m),r?.addEventListener("keydown",p=>{p.key==="Enter"&&m()})}),e.find("#connections-raw-toggle").on("change",function(){if(this.checked)i=w(e),v(e),e.find("#connections-form-view").hide(),e.find("#connections-raw-view").show();else{const t=e.find("#connections-raw-json").get(0)?.value||"{}";try{i=JSON.parse(t),C(e),e.find("#connections-form-view").show(),e.find("#connections-raw-view").hide()}catch{E.toast("Invalid JSON \u2014 fix before switching to form view.",{type:"warning"}),this.checked=!0}}}),e.find("#add-connection-btn").on("click",()=>{i=w(e);let t=Object.keys(i).length+1,n=`connection${t}`;for(;i[n];)t++,n=`connection${t}`;i[n]={type:"mongodb",uri:"",database:"",options:{}},C(e),v(e)}),e.find("#save-connections-btn").on("click",async()=>{const t=e.find("#connections-raw-toggle").get(0)?.checked;let n;if(t)try{n=JSON.parse(e.find("#connections-raw-json").get(0)?.value||"{}")}catch{E.toast("Invalid JSON \u2014 fix before saving.",{type:"warning"});return}else n=w(e);try{await h.collections.saveConnections(n),i=n,E.toast("Connections saved.",{type:"success"})}catch(c){E.toast(c.message||"Failed to save connections.",{type:"error"})}}),Domma.icons.scan()}};async function j(e){let a=[];try{a=await h.collections.list()}catch{E.toast("Could not load collections.",{type:"error"})}T.create("#collections-table",{data:a,columns:[{key:"title",title:"Title",render:(o,t)=>{const n=document.createElement("a");return n.href=`#/collections/${b(t.slug)}/entries`,n.textContent=o,n.style.fontWeight="600",n.outerHTML}},{key:"slug",title:"Slug",render:o=>{const t=document.createElement("code");return t.textContent=o,t.outerHTML}},{key:"fields",title:"Field Count",render:o=>String(o?.length??0)},{key:"entryCount",title:"Entry Count",render:o=>String(o??0)},{key:"slug",title:"Actions",render:o=>{const t=document.createElement("div");t.style.cssText="display:flex;gap:.4rem;justify-content:flex-end;flex-wrap:wrap;";const n=document.createElement("a");n.href=`#/collections/edit/${b(o)}`,n.className="btn btn-sm btn-ghost",n.textContent="Edit Schema";const c=document.createElement("a");if(c.href=`#/collections/${b(o)}/entries`,c.className="btn btn-sm btn-ghost",c.textContent="Entries",t.appendChild(n),t.appendChild(c),!new Set(["roles","user-profiles"]).has(o)){const l=document.createElement("a");l.href=`#/forms/edit/${b(o)}`,l.className="btn btn-sm btn-ghost",l.textContent="Form",t.appendChild(l)}const s=document.createElement("button");return s.className="btn btn-sm btn-danger js-delete-collection",s.dataset.slug=o,s.textContent="Delete",t.appendChild(s),t.outerHTML}}],emptyMessage:'No collections yet. Click "New Collection" to get started.'}),document.querySelectorAll(".js-delete-collection").forEach(o=>{o.addEventListener("click",async()=>{const t=o.dataset.slug;if(await E.confirm(`Delete collection "${t}" and all its data? This cannot be undone.`))try{await h.collections.delete(t),E.toast("Collection deleted.",{type:"success"}),await j(e)}catch{E.toast("Failed to delete collection.",{type:"error"})}})}),Domma.icons.scan()}async function q(e){try{i=await h.collections.getConnections()}catch{i={}}C(e),v(e)}function C(e){const a=e.find("#connections-list").get(0),o=e.find("#connections-empty").get(0);if(!a)return;a.textContent="";const t=Object.keys(i);o&&(o.style.display=t.length?"none":""),t.forEach(n=>{const c=i[n],d=document.createElement("div");d.className="conn-card card mb-3",d.dataset.connKey=n;const s=document.createElement("div");s.className="card-header",s.style.cssText="display:flex;align-items:center;gap:.5rem;";const l=document.createElement("input");l.type="text",l.className="form-input conn-name",l.value=n,l.placeholder="Connection name",l.style.cssText="flex:1;";const r=document.createElement("button");r.type="button",r.className="btn btn-sm btn-danger",r.textContent="Delete",r.addEventListener("click",()=>{i=w(e);const L=l.value.trim();L&&delete i[L],C(e),v(e)}),s.appendChild(l),s.appendChild(r);const m=document.createElement("div");m.className="card-body",m.style.cssText="display:grid;grid-template-columns:1fr 2fr 1fr;gap:.6rem;";const p=document.createElement("div"),u=document.createElement("label");u.className="form-label",u.textContent="Type";const f=document.createElement("input");f.type="text",f.className="form-input conn-type",f.value=c.type||"mongodb",f.disabled=!0,p.appendChild(u),p.appendChild(f);const x=document.createElement("div"),S=document.createElement("label");S.className="form-label",S.textContent="URI";const y=document.createElement("input");y.type="text",y.className="form-input conn-uri",y.value=c.uri||"",y.placeholder="mongodb://localhost:27017",x.appendChild(S),x.appendChild(y);const N=document.createElement("div"),k=document.createElement("label");k.className="form-label",k.textContent="Database";const g=document.createElement("input");g.type="text",g.className="form-input conn-database",g.value=c.database||"",g.placeholder="mydb",N.appendChild(k),N.appendChild(g),m.appendChild(p),m.appendChild(x),m.appendChild(N),d.appendChild(s),d.appendChild(m),a.appendChild(d)})}function w(e){const a={},o=e.find("#connections-list").get(0)?.querySelectorAll(".conn-card")||[];for(const t of o){const n=t.querySelector(".conn-name")?.value.trim(),c=t.querySelector(".conn-uri")?.value.trim()||"",d=t.querySelector(".conn-database")?.value.trim()||"";n&&(a[n]={type:"mongodb",uri:c,database:d,options:{}})}return a}function v(e){const a=e.find("#connections-raw-json").get(0);a&&(a.value=JSON.stringify(i,null,2))}
|
|
@@ -1,56 +1 @@
|
|
|
1
|
-
|
|
2
|
-
* Dashboard View
|
|
3
|
-
* Shows page count stats and a recent pages table.
|
|
4
|
-
*/
|
|
5
|
-
import {api} from '../api.js';
|
|
6
|
-
|
|
7
|
-
export const dashboardView = {
|
|
8
|
-
templateUrl: '/admin/js/templates/dashboard.html',
|
|
9
|
-
|
|
10
|
-
async onMount($container) {
|
|
11
|
-
const pages = await api.pages.list().catch(() => []);
|
|
12
|
-
const total = pages.length;
|
|
13
|
-
const published = pages.filter(p => p.status === 'published').length;
|
|
14
|
-
const drafts = pages.filter(p => p.status === 'draft').length;
|
|
15
|
-
|
|
16
|
-
$container.find('#stat-total').text(total);
|
|
17
|
-
$container.find('#stat-published').text(published);
|
|
18
|
-
$container.find('#stat-drafts').text(drafts);
|
|
19
|
-
|
|
20
|
-
Domma.effects.counter('#stat-total, #stat-published, #stat-drafts', {
|
|
21
|
-
trigger: 'immediate',
|
|
22
|
-
duration: 1000,
|
|
23
|
-
stagger: 120,
|
|
24
|
-
easing: 'ease-out'
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
const recent = [...pages]
|
|
28
|
-
.sort((a, b) => (b.updatedAt || '').localeCompare(a.updatedAt || ''))
|
|
29
|
-
.slice(0, 10);
|
|
30
|
-
|
|
31
|
-
T.create('#recent-pages-table', {
|
|
32
|
-
data: recent,
|
|
33
|
-
columns: [
|
|
34
|
-
{key: 'title', title: 'Title'},
|
|
35
|
-
{key: 'urlPath', title: 'URL'},
|
|
36
|
-
{
|
|
37
|
-
key: 'status',
|
|
38
|
-
title: 'Status',
|
|
39
|
-
render: (val) => `<span class="badge badge-${val === 'published' ? 'success' : 'warning'}">${val}</span>`
|
|
40
|
-
},
|
|
41
|
-
{key: 'updatedAt', title: 'Updated', render: (val) => val ? D(val).format('DD MMM YYYY') : '—'},
|
|
42
|
-
{
|
|
43
|
-
key: 'urlPath',
|
|
44
|
-
title: '',
|
|
45
|
-
render: (val) => `<a href="#/pages/edit${val}" class="btn btn-sm btn-outline">Edit</a>`
|
|
46
|
-
}
|
|
47
|
-
],
|
|
48
|
-
emptyMessage: 'No pages yet. <a href="#/pages/new">Create your first page</a>.'
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
Domma.icons.scan();
|
|
52
|
-
|
|
53
|
-
Domma.effects.reveal('.stat-card', { animation: 'fade', stagger: 80, duration: 400 });
|
|
54
|
-
Domma.effects.reveal('.card.mt-4', { animation: 'fade', delay: 200, duration: 400 });
|
|
55
|
-
}
|
|
56
|
-
};
|
|
1
|
+
import{api as n}from"../api.js";export const dashboardView={templateUrl:"/admin/js/templates/dashboard.html",async onMount(e){const s=E.loader(e.get(0),{type:"dots"}),a=await n.pages.list().catch(()=>[]);s.destroy();const d=a.length,r=a.filter(t=>t.status==="published").length,i=a.filter(t=>t.status==="draft").length;e.find("#stat-total").text(d),e.find("#stat-published").text(r),e.find("#stat-drafts").text(i),Domma.effects.counter("#stat-total, #stat-published, #stat-drafts",{trigger:"immediate",duration:1e3,stagger:120,easing:"ease-out"});const o=[...a].sort((t,l)=>(l.updatedAt||"").localeCompare(t.updatedAt||"")).slice(0,10);T.create("#recent-pages-table",{data:o,columns:[{key:"title",title:"Title"},{key:"urlPath",title:"URL"},{key:"status",title:"Status",render:t=>`<span class="badge badge-${t==="published"?"success":"warning"}">${t}</span>`},{key:"updatedAt",title:"Updated",render:t=>t?D(t).format("DD MMM YYYY"):"\u2014"},{key:"urlPath",title:"Actions",render:t=>`<a href="#/pages/edit${t}" class="btn btn-sm btn-primary">Edit</a>`}],emptyMessage:'No pages yet. <a href="#/pages/new">Create your first page</a>.'}),Domma.icons.scan(),Domma.effects.reveal(".stat-card",{animation:"fade",stagger:80,duration:400}),Domma.effects.reveal(".card.mt-4",{animation:"fade",delay:200,duration:400})}};
|
|
@@ -1,12 +1 @@
|
|
|
1
|
-
|
|
2
|
-
* Documentation View
|
|
3
|
-
* Usage reference for Domma CMS — content, structure, plugins, settings.
|
|
4
|
-
*/
|
|
5
|
-
export const documentationView = {
|
|
6
|
-
templateUrl: '/admin/js/templates/documentation.html',
|
|
7
|
-
|
|
8
|
-
async onMount($container) {
|
|
9
|
-
Domma.icons.scan();
|
|
10
|
-
Domma.syntax.scan();
|
|
11
|
-
}
|
|
12
|
-
};
|
|
1
|
+
export const documentationView={templateUrl:"/admin/js/templates/documentation.html",async onMount(n){Domma.icons.scan(),Domma.syntax.scan()}};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import{apiRequest as T}from"/admin/js/api.js";const z=[{value:"string",label:"Text (single line)"},{value:"email",label:"Email"},{value:"tel",label:"Phone"},{value:"number",label:"Number"},{value:"textarea",label:"Textarea (multi-line)"},{value:"select",label:"Dropdown (select)"},{value:"radio",label:"Radio buttons"},{value:"checkbox",label:"Single checkbox"},{value:"checkbox-group",label:"Checkbox group"},{value:"date",label:"Date"},{value:"time",label:"Time"},{value:"url",label:"URL"},{value:"password",label:"Password"},{value:"file",label:"File upload"},{value:"hidden",label:"Hidden field"}],I=new Set(["select","radio","checkbox-group"]);let b=[],C=null,L=null,q=null;function O(e){return e.toLowerCase().replace(/[^a-z0-9]+/g,"_").replace(/^_|_$/g,"")}function P(e){return z.find(t=>t.value===e)?.label||e}function M(e){return e?!!(e.visibility?.conditions?.length||e.requirement?.conditions?.length||e.validation?.length||e.cascade?.sourceField):!1}function G(e){const t={...b[e]};if(t.type==="spacer")return t;if(t.type==="page-break"){const g=document.getElementById(`fb-pb-label-${e}`),h=document.getElementById(`fb-pb-desc-${e}`);return g&&(t.label=g.value.trim()||t.label),h&&(t.description=h.value.trim()),t}const c=document.getElementById(`fb-label-${e}`),n=document.getElementById(`fb-name-${e}`),s=document.getElementById(`fb-type-${e}`),a=document.getElementById(`fb-required-${e}`),l=document.getElementById(`fb-placeholder-${e}`),d=document.getElementById(`fb-helper-${e}`);if(c&&(t.label=c.value.trim()||t.label),n&&(t.name=n.value.trim()||t.name),s&&(t.type=s.value||t.type),a&&(t.required=a.checked),l&&(t.placeholder=l.value.trim()),d&&(t.helper=d.value.trim()),I.has(t.type)){const g=document.getElementById(`fb-options-${e}`);g&&(t.options=g.value.split(`
|
|
2
|
+
`).filter(h=>h.trim()).map(h=>{const[y,...S]=h.split(":");return{value:y.trim(),label:S.join(":").trim()||y.trim()}}))}if(t.type==="textarea"){const g=parseInt(document.getElementById(`fb-rows-${e}`)?.value,10);g>0&&(t.formConfig={...t.formConfig||{},rows:g})}const o=document.getElementById(`fb-span-${e}`),i=document.getElementById(`fb-fullwidth-${e}`);if(o||i){const g={...t.formConfig||{}};if(i?.checked)g.span="full";else{const h=parseInt(o?.value,10);h>1?g.span=h:delete g.span}t.formConfig=Object.keys(g).length?g:void 0}const m=document.getElementById(`fb-minlength-${e}`)?.value;m&&(t.minLength=parseInt(m,10));const p=document.getElementById(`fb-maxlength-${e}`)?.value;p&&(t.maxLength=parseInt(p,10));const f=document.getElementById(`fb-min-${e}`)?.value;f!==""&&f!==void 0&&(t.min=parseFloat(f));const r=document.getElementById(`fb-max-${e}`)?.value;r!==""&&r!==void 0&&(t.max=parseFloat(r));const u=J(e);return u?t.logic=u:delete t.logic,t}function J(e){const t=document.querySelector(`.fb-field-card[data-index="${e}"]`);if(!t)return;const c=t.querySelector(".fb-field-logic");if(!c)return;const n={};let s=!1;const a=c.querySelector('[data-logic-section="visibility"]');if(a){const i=a.querySelector(".fb-logic-vis-default"),m=a.querySelector(".fb-logic-vis-transition"),p=Array.from(a.querySelectorAll(".fb-logic-cond-row")).map(u=>{const g=u.querySelector(".fb-logic-cond-field"),h=u.querySelector(".fb-logic-cond-op"),y=u.querySelector(".fb-logic-cond-val"),S=u.querySelector(".fb-logic-vis-then");return g?.value?{when:{all:[{field:g.value,operator:h.value,value:y.value}]},then:S.value}:null}).filter(Boolean),f=i?.value||"visible",r=m?.value||"none";(f!=="visible"||p.length>0||r!=="none")&&(n.visibility={default:f,conditions:p},r!=="none"&&(n.visibility.transition=r),s=!0)}const l=c.querySelector('[data-logic-section="requirement"]');if(l){const i=l.querySelector(".fb-logic-req-default"),m=Array.from(l.querySelectorAll(".fb-logic-cond-row")).map(p=>{const f=p.querySelector(".fb-logic-cond-field"),r=p.querySelector(".fb-logic-cond-op"),u=p.querySelector(".fb-logic-cond-val"),g=p.querySelector(".fb-logic-req-then");return f?.value?{when:{all:[{field:f.value,operator:r.value,value:u.value}]},then:g.value==="true"}:null}).filter(Boolean);m.length>0&&(n.requirement={default:i?.checked===!0,conditions:m},s=!0)}const d=c.querySelector('[data-logic-section="validation"]');if(d){const i=Array.from(d.querySelectorAll(".fb-logic-val-rule")).map(m=>{const p=m.querySelector(".fb-logic-val-type"),f=m.querySelector(".fb-logic-val-pattern"),r=m.querySelector(".fb-logic-val-flags"),u=m.querySelector(".fb-logic-val-message");if(!f?.value.trim())return null;const g=p?.value||"regex",h={type:g,message:u?.value.trim()||"Invalid value."};return g==="regex"?(h.pattern=f.value.trim(),r?.value.trim()&&(h.flags=r.value.trim())):h.field=f.value.trim(),h}).filter(Boolean);i.length>0&&(n.validation=i,s=!0)}const o=c.querySelector('[data-logic-section="cascade"]');if(o){const i=o.querySelector(".fb-logic-cascade-source"),m=o.querySelector(".fb-logic-cascade-mapping"),p=o.querySelector(".fb-logic-cascade-defaults"),f=i?.value?.trim();if(f){let r={};try{r=JSON.parse(m?.value||"{}")}catch{}const u=(p?.value||"").split(`
|
|
3
|
+
`).filter(g=>g.trim()).map(g=>{const[h,...y]=g.split(":");return{value:h.trim(),label:y.join(":").trim()||h.trim()}});n.cascade={sourceField:f,mapping:r,defaultOptions:u},s=!0}}return s?n:void 0}function v(){b=b.map((e,t)=>G(t))}function x(e){const t=e.find("#fields-list").get(0),c=e.find("#fields-empty-msg").get(0);if(t){if(Array.from(t.querySelectorAll(".fb-field-card")).forEach(n=>n.remove()),b.length===0){c&&(c.style.display="");return}c&&(c.style.display="none"),b.forEach((n,s)=>{const a=n.type==="page-break"?W(n,s,e):n.type==="spacer"?Y(n,s,e):K(n,s,e);t.appendChild(a)})}}function W(e,t,c){const n=document.createElement("div");n.className="fb-field-card",n.dataset.index=t,n.style.cssText="border:2px dashed var(--border-color,#444);border-radius:6px;overflow:hidden;margin-bottom:.5rem;background:var(--card-bg,rgba(255,255,255,.02));";const s=document.createElement("div");s.style.cssText="display:flex;align-items:center;gap:.6rem;padding:.6rem .8rem;cursor:pointer;user-select:none;";const a=document.createElement("span");a.textContent="\u2014 Page Break \u2014",a.style.cssText="font-size:.7rem;padding:.15rem .5rem;border-radius:999px;background:rgba(120,120,120,.2);color:var(--text-muted,#888);white-space:nowrap;flex-shrink:0;font-style:italic;";const l=document.createElement("span");l.textContent=e.label||"Untitled Step",l.style.cssText="flex:1;font-weight:600;font-size:.9rem;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--text-muted,#888);";const d=document.createElement("div");if(d.style.cssText="display:flex;gap:.25rem;flex-shrink:0;margin-left:.5rem;",t>0){const r=document.createElement("button");r.className="btn btn-xs btn-ghost",r.title="Move up",r.textContent="\u2191",r.addEventListener("click",u=>{u.stopPropagation(),v(),[b[t-1],b[t]]=[b[t],b[t-1]],x(c)}),d.appendChild(r)}if(t<b.length-1){const r=document.createElement("button");r.className="btn btn-xs btn-ghost",r.title="Move down",r.textContent="\u2193",r.addEventListener("click",u=>{u.stopPropagation(),v(),[b[t],b[t+1]]=[b[t+1],b[t]],x(c)}),d.appendChild(r)}const o=document.createElement("button");o.className="btn btn-xs btn-ghost",o.title="Edit step",o.textContent="\u22EF",o.addEventListener("click",r=>{r.stopPropagation(),m.style.display=m.style.display==="none"?"":"none"}),d.appendChild(o);const i=document.createElement("button");i.className="btn btn-xs btn-danger",i.title="Remove page break",i.textContent="\u2715",i.addEventListener("click",async r=>{r.stopPropagation(),await E.confirm("Remove this page break?")&&(v(),b.splice(t,1),x(c))}),d.appendChild(i),s.appendChild(a),s.appendChild(l),s.appendChild(d),s.addEventListener("click",()=>{m.style.display=m.style.display==="none"?"":"none"});const m=document.createElement("div");m.className="fb-field-body",m.style.cssText="padding:.8rem;border-top:1px dashed var(--border-color,#444);display:none;";const p=k([w("Step Title",`fb-pb-label-${t}`,"text",e.label||"","Shown as the wizard step heading"),w("Step Description",`fb-pb-desc-${t}`,"text",e.description||"","Optional sub-heading")]),f=p.querySelector(`#fb-pb-label-${t}`);return f&&f.addEventListener("input",()=>{l.textContent=f.value||"Untitled Step"}),m.appendChild(p),n.appendChild(s),n.appendChild(m),n}function Y(e,t,c){const n=document.createElement("div");n.className="fb-field-card",n.dataset.index=t,n.style.cssText="border:1px dashed var(--border-color,#444);border-radius:6px;margin-bottom:.5rem;background:transparent;";const s=document.createElement("div");s.style.cssText="display:flex;align-items:center;gap:.6rem;padding:.4rem .8rem;";const a=document.createElement("div");a.style.cssText="flex:1;height:1px;background:var(--border-color,#444);";const l=document.createElement("span");l.textContent="Spacer",l.style.cssText="font-size:.7rem;color:var(--text-muted,#888);white-space:nowrap;padding:0 .4rem;font-style:italic;";const d=document.createElement("div");d.style.cssText="flex:1;height:1px;background:var(--border-color,#444);";const o=document.createElement("div");if(o.style.cssText="display:flex;gap:.25rem;flex-shrink:0;",t>0){const m=document.createElement("button");m.className="btn btn-xs btn-ghost",m.title="Move up",m.textContent="\u2191",m.addEventListener("click",p=>{p.stopPropagation(),v(),[b[t-1],b[t]]=[b[t],b[t-1]],x(c)}),o.appendChild(m)}if(t<b.length-1){const m=document.createElement("button");m.className="btn btn-xs btn-ghost",m.title="Move down",m.textContent="\u2193",m.addEventListener("click",p=>{p.stopPropagation(),v(),[b[t],b[t+1]]=[b[t+1],b[t]],x(c)}),o.appendChild(m)}const i=document.createElement("button");return i.className="btn btn-xs btn-danger",i.title="Remove spacer",i.textContent="\u2715",i.addEventListener("click",async m=>{m.stopPropagation(),v(),b.splice(t,1),x(c)}),o.appendChild(i),s.appendChild(a),s.appendChild(l),s.appendChild(d),s.appendChild(o),n.appendChild(s),n}function K(e,t,c){const n=document.createElement("div");n.className="fb-field-card",n.dataset.index=t,n.style.cssText="border:1px solid var(--border-color,#333);border-radius:6px;overflow:hidden;margin-bottom:.5rem;";const s=document.createElement("div");s.style.cssText="display:flex;align-items:center;gap:.6rem;padding:.6rem .8rem;background:var(--card-header-bg,rgba(255,255,255,.04));cursor:pointer;user-select:none;";const a=document.createElement("span");a.textContent=P(e.type),a.style.cssText="font-size:.7rem;padding:.15rem .45rem;border-radius:999px;background:var(--primary-soft,rgba(99,102,241,.15));color:var(--primary,#6366f1);white-space:nowrap;flex-shrink:0;";const l=document.createElement("span");l.textContent=e.label||"(unlabelled)",l.style.cssText="flex:1;font-weight:600;font-size:.9rem;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;";const d=document.createElement("div");if(d.style.cssText="display:flex;gap:.25rem;flex-shrink:0;margin-left:.5rem;",t>0){const p=document.createElement("button");p.className="btn btn-xs btn-ghost",p.title="Move up",p.textContent="\u2191",p.addEventListener("click",f=>{f.stopPropagation(),v(),[b[t-1],b[t]]=[b[t],b[t-1]],x(c)}),d.appendChild(p)}if(t<b.length-1){const p=document.createElement("button");p.className="btn btn-xs btn-ghost",p.title="Move down",p.textContent="\u2193",p.addEventListener("click",f=>{f.stopPropagation(),v(),[b[t],b[t+1]]=[b[t+1],b[t]],x(c)}),d.appendChild(p)}const o=document.createElement("button");o.className="btn btn-xs btn-ghost",o.title="Edit field",o.textContent="\u22EF",o.addEventListener("click",p=>{p.stopPropagation(),m.style.display=m.style.display==="none"?"":"none"}),d.appendChild(o);const i=document.createElement("button");if(i.className="btn btn-xs btn-danger",i.title="Remove field",i.textContent="\u2715",i.addEventListener("click",async p=>{p.stopPropagation(),await E.confirm("Remove this field?")&&(v(),b.splice(t,1),x(c))}),d.appendChild(i),s.appendChild(a),s.appendChild(l),e.required){const p=document.createElement("span");p.textContent="required",p.style.cssText="font-size:.7rem;color:var(--danger,#ef4444);flex-shrink:0;",s.appendChild(p)}if(M(e.logic)){const p=document.createElement("span");p.textContent="\u26A1",p.title="Has conditional logic",p.style.cssText="font-size:.75rem;color:var(--primary,#6366f1);flex-shrink:0;",s.appendChild(p)}s.appendChild(d),s.addEventListener("click",()=>{m.style.display=m.style.display==="none"?"":"none"});const m=Q(e,t,l);return m.style.display="none",n.appendChild(s),n.appendChild(m),n}function Q(e,t,c){const n=document.createElement("div");n.className="fb-field-body",n.style.cssText="padding:.8rem;border-top:1px solid var(--border-color,#333);";const s=k([w("Label",`fb-label-${t}`,"text",e.label||"","Shown above the field"),w("Field Name",`fb-name-${t}`,"text",e.name||"","Used as data key")]),a=k([le("Type",`fb-type-${t}`,z,e.type||"string"),D("Required",`fb-required-${t}`,e.required||!1)]),l=k([w("Placeholder",`fb-placeholder-${t}`,"text",e.placeholder||"","Hint text inside the field"),w("Helper Text",`fb-helper-${t}`,"text",e.helper||"","Shown below the field")]),d=e.formConfig?.span,o=k([w("Column Span",`fb-span-${t}`,"number",d&&d!=="full"?String(d):"1","Columns to span (grid only)"),D("Full Width",`fb-fullwidth-${t}`,d==="full")]);o.classList.add("fb-grid-row"),o.style.display=document.getElementById("setting-layout")?.value==="grid"?"flex":"none",n.appendChild(s),n.appendChild(a),n.appendChild(l),n.appendChild(o);const i=n.querySelector(`#fb-label-${t}`),m=n.querySelector(`#fb-name-${t}`);i&&i.addEventListener("input",()=>{c&&(c.textContent=i.value||"(unlabelled)"),m&&!m.dataset.manuallyEdited&&(m.value=O(i.value))}),m&&m.addEventListener("input",()=>{m.dataset.manuallyEdited="1"});const p=n.querySelector(`#fb-type-${t}`);p&&p.addEventListener("change",()=>{const r=n.closest(".fb-field-card");if(r){const h=r.querySelector("span");h&&(h.textContent=P(p.value))}const u=n.querySelector(".fb-field-extras");u&&u.remove();const g=j(p.value,e,t);g&&n.appendChild(g)});const f=j(e.type,e,t);return f&&n.appendChild(f),n.appendChild(ne(e,t)),n}const X=[{value:"equals",label:"equals"},{value:"not_equals",label:"does not equal"},{value:"contains",label:"contains"},{value:"not_contains",label:"does not contain"},{value:"starts_with",label:"starts with"},{value:"ends_with",label:"ends with"},{value:"greater_than",label:"is greater than"},{value:"less_than",label:"is less than"},{value:"is_empty",label:"is empty"},{value:"is_not_empty",label:"is not empty"},{value:"in",label:"is one of (comma sep)"},{value:"not_in",label:"is not one of (comma sep)"},{value:"matches_regex",label:"matches regex"}],_=new Set(["is_empty","is_not_empty"]);function N(e){const t=document.createElement("p");return t.textContent=e,t.style.cssText="font-size:.75rem;font-weight:700;color:var(--text-muted,#888);margin:.6rem 0 .3rem;text-transform:uppercase;letter-spacing:.04em;",t}function B(e,t,c,n,s){const a=document.createElement("div");a.className="fb-logic-cond-row",a.style.cssText="display:flex;gap:.35rem;align-items:center;margin-bottom:.35rem;flex-wrap:wrap;";const l=document.createElement("span");l.textContent="When",l.style.cssText="font-size:.73rem;color:var(--text-muted,#888);flex-shrink:0;";const d=document.createElement("select");d.className="form-input fb-logic-cond-field",d.style.cssText="flex:2;min-width:80px;font-size:.78rem;padding:.2rem .35rem;",t.forEach(r=>{const u=document.createElement("option");u.value=r.value,u.textContent=r.label,e&&r.value===e.field&&(u.selected=!0),d.appendChild(u)});const o=document.createElement("select");o.className="form-input fb-logic-cond-op",o.style.cssText="flex:2;min-width:80px;font-size:.78rem;padding:.2rem .35rem;",X.forEach(r=>{const u=document.createElement("option");u.value=r.value,u.textContent=r.label,e&&r.value===e.operator&&(u.selected=!0),o.appendChild(u)});const i=document.createElement("input");i.type="text",i.className="form-input fb-logic-cond-val",i.placeholder="value",i.style.cssText="flex:2;min-width:60px;font-size:.78rem;padding:.2rem .35rem;",i.value=e?.value||"",e&&_.has(e.operator)&&(i.style.display="none"),o.addEventListener("change",()=>{i.style.display=_.has(o.value)?"none":""});const m=document.createElement("span");m.textContent="\u2192",m.style.cssText="font-size:.73rem;color:var(--text-muted,#888);flex-shrink:0;";const p=document.createElement("select");p.className=`form-input ${n}`,p.style.cssText="flex:2;min-width:80px;font-size:.78rem;padding:.2rem .35rem;",c.forEach(r=>{const u=document.createElement("option");u.value=r.value,u.textContent=r.label,r.value===s&&(u.selected=!0),p.appendChild(u)});const f=document.createElement("button");return f.type="button",f.className="btn btn-xs btn-danger",f.textContent="\u2715",f.style.flexShrink="0",f.addEventListener("click",()=>a.remove()),a.appendChild(l),a.appendChild(d),a.appendChild(o),a.appendChild(i),a.appendChild(m),a.appendChild(p),a.appendChild(f),a}function Z(e,t,c){const n=document.createElement("div");n.dataset.logicSection="visibility",n.appendChild(N("Visibility"));const s=document.createElement("div");s.style.cssText="display:flex;align-items:center;gap:.5rem;margin-bottom:.4rem;";const a=document.createElement("span");a.textContent="Default:",a.style.cssText="font-size:.8rem;flex-shrink:0;";const l=document.createElement("select");l.className="form-input fb-logic-vis-default",l.style.cssText="font-size:.8rem;padding:.25rem .4rem;",[{value:"visible",label:"Visible"},{value:"hidden",label:"Hidden"}].forEach(r=>{const u=document.createElement("option");u.value=r.value,u.textContent=r.label,r.value===(e.default||"visible")&&(u.selected=!0),l.appendChild(u)}),s.appendChild(a),s.appendChild(l),n.appendChild(s);const d=document.createElement("div");d.style.cssText="display:flex;align-items:center;gap:.5rem;margin-bottom:.4rem;";const o=document.createElement("span");o.textContent="Transition:",o.style.cssText="font-size:.8rem;flex-shrink:0;";const i=document.createElement("select");i.className="form-input fb-logic-vis-transition",i.style.cssText="font-size:.8rem;padding:.25rem .4rem;",[{value:"none",label:"None (instant)"},{value:"fade",label:"Fade"},{value:"slide",label:"Slide"},{value:"scale",label:"Scale"}].forEach(r=>{const u=document.createElement("option");u.value=r.value,u.textContent=r.label,r.value===(e.transition||"none")&&(u.selected=!0),i.appendChild(u)}),d.appendChild(o),d.appendChild(i),n.appendChild(d);const m=document.createElement("div");m.className="fb-logic-vis-rules";const p=c.map(r=>({value:r.name,label:r.label||r.name})),f=[{value:"visible",label:"Show"},{value:"hidden",label:"Hide"}];if((e.conditions||[]).forEach(r=>{const u=(r.when?.all||r.when?.any||[])[0],g=r.then==="hidden"?"hidden":"visible";p.length>0&&m.appendChild(B(u,p,f,"fb-logic-vis-then",g))}),n.appendChild(m),p.length>0){const r=document.createElement("button");r.type="button",r.className="btn btn-xs btn-ghost",r.style.cssText="font-size:.73rem;margin-top:.2rem;",r.textContent="+ Add visibility rule",r.addEventListener("click",()=>m.appendChild(B(null,p,f,"fb-logic-vis-then","visible"))),n.appendChild(r)}return n}function $(e,t,c){const n=document.createElement("div");n.dataset.logicSection="requirement",n.appendChild(N("Conditional Requirement"));const s=document.createElement("label");s.style.cssText="display:flex;align-items:center;gap:.4rem;font-size:.8rem;cursor:pointer;margin-bottom:.4rem;";const a=document.createElement("input");a.type="checkbox",a.className="fb-logic-req-default",a.checked=e.default===!0,s.appendChild(a),s.appendChild(document.createTextNode("Required by default")),n.appendChild(s);const l=document.createElement("div");l.className="fb-logic-req-rules";const d=c.map(i=>({value:i.name,label:i.label||i.name})),o=[{value:"true",label:"Make required"},{value:"false",label:"Make optional"}];if((e.conditions||[]).forEach(i=>{const m=(i.when?.all||i.when?.any||[])[0],p=i.then===!0?"true":"false";d.length>0&&l.appendChild(B(m,d,o,"fb-logic-req-then",p))}),n.appendChild(l),d.length>0){const i=document.createElement("button");i.type="button",i.className="btn btn-xs btn-ghost",i.style.cssText="font-size:.73rem;margin-top:.2rem;",i.textContent="+ Add requirement rule",i.addEventListener("click",()=>l.appendChild(B(null,d,o,"fb-logic-req-then","true"))),n.appendChild(i)}return n}function A(e){const t=document.createElement("div");t.className="fb-logic-val-rule",t.style.cssText="display:flex;gap:.35rem;align-items:center;margin-bottom:.35rem;flex-wrap:wrap;";const c=document.createElement("select");c.className="form-input fb-logic-val-type",c.style.cssText="flex:0 0 auto;font-size:.78rem;padding:.2rem .35rem;",[{value:"regex",label:"Regex"},{value:"match",label:"Match field"}].forEach(d=>{const o=document.createElement("option");o.value=d.value,o.textContent=d.label,d.value===(e?.type||"regex")&&(o.selected=!0),c.appendChild(o)});const n=document.createElement("input");n.type="text",n.className="form-input fb-logic-val-pattern",n.placeholder=e?.type==="match"?"field name":"pattern",n.value=e?.pattern||e?.field||"",n.style.cssText="flex:3;font-size:.78rem;padding:.2rem .35rem;";const s=document.createElement("input");s.type="text",s.className="form-input fb-logic-val-flags",s.placeholder="flags",s.value=e?.flags||"",s.style.cssText="flex:0 0 55px;font-size:.78rem;padding:.2rem .35rem;",e?.type==="match"&&(s.style.display="none"),c.addEventListener("change",()=>{s.style.display=c.value==="match"?"none":"",n.placeholder=c.value==="match"?"field name":"pattern"});const a=document.createElement("input");a.type="text",a.className="form-input fb-logic-val-message",a.placeholder="Error message",a.value=e?.message||"",a.style.cssText="flex:4;font-size:.78rem;padding:.2rem .35rem;";const l=document.createElement("button");return l.type="button",l.className="btn btn-xs btn-danger",l.textContent="\u2715",l.style.flexShrink="0",l.addEventListener("click",()=>t.remove()),t.appendChild(c),t.appendChild(n),t.appendChild(s),t.appendChild(a),t.appendChild(l),t}function ee(e,t,c){const n=document.createElement("div");n.dataset.logicSection="validation",n.appendChild(N("Custom Validation"));const s=document.createElement("div");s.className="fb-logic-val-rules",(e||[]).forEach(l=>s.appendChild(A(l))),n.appendChild(s);const a=document.createElement("button");return a.type="button",a.className="btn btn-xs btn-ghost",a.style.cssText="font-size:.73rem;margin-top:.2rem;",a.textContent="+ Add validation rule",a.addEventListener("click",()=>s.appendChild(A(null))),n.appendChild(a),n}function te(e,t,c){const n=document.createElement("div");n.dataset.logicSection="cascade",n.appendChild(N("Cascade Options"));const s=document.createElement("div");s.style.cssText="display:flex;align-items:center;gap:.5rem;margin-bottom:.5rem;";const a=document.createElement("span");a.textContent="Source field:",a.style.cssText="font-size:.8rem;flex-shrink:0;";const l=document.createElement("select");l.className="form-input fb-logic-cascade-source",l.style.cssText="flex:1;font-size:.8rem;padding:.25rem .4rem;";const d=document.createElement("option");d.value="",d.textContent="\u2014 none \u2014",l.appendChild(d),c.forEach(f=>{const r=document.createElement("option");r.value=f.name,r.textContent=f.label||f.name,f.name===e.sourceField&&(r.selected=!0),l.appendChild(r)}),s.appendChild(a),s.appendChild(l),n.appendChild(s);const o=document.createElement("p");o.textContent='Mapping JSON \u2014 {"value":[{"value":"...","label":"..."}]}',o.style.cssText="font-size:.73rem;color:var(--text-muted,#888);margin:.3rem 0 .2rem;";const i=document.createElement("textarea");i.className="form-input fb-logic-cascade-mapping",i.rows=4,i.style.cssText="font-family:monospace;font-size:.78rem;",i.placeholder='{"uk": [{"value": "london", "label": "London"}]}',i.value=e.mapping?JSON.stringify(e.mapping,null,2):"",n.appendChild(o),n.appendChild(i);const m=document.createElement("p");m.textContent="Default options (one per line: value:Label)",m.style.cssText="font-size:.73rem;color:var(--text-muted,#888);margin:.3rem 0 .2rem;";const p=document.createElement("textarea");return p.className="form-input fb-logic-cascade-defaults",p.rows=3,p.style.cssText="font-family:monospace;font-size:.78rem;",p.placeholder=`option1:Option 1
|
|
4
|
+
option2:Option 2`,p.value=(e.defaultOptions||[]).map(f=>{const r=typeof f=="string"?f:f.value??"",u=typeof f=="string"?f:f.label??r;return r===u?r:`${r}:${u}`}).join(`
|
|
5
|
+
`),n.appendChild(m),n.appendChild(p),n}function ne(e,t){const c=e.logic||{},n=b.filter((p,f)=>f!==t&&p.type!=="page-break"&&p.type!=="spacer"),s=document.createElement("div");s.className="fb-field-logic",s.style.cssText="margin-top:.75rem;border-top:1px solid var(--border-color,#333);padding-top:.5rem;";const a=document.createElement("div");a.style.cssText="display:flex;align-items:center;justify-content:space-between;cursor:pointer;padding:.15rem 0;";const l=document.createElement("span");l.style.cssText="font-size:.8rem;font-weight:600;color:var(--text-muted,#888);",l.textContent="\u26A1 Conditional Logic";const d=M(c),o=document.createElement("button");o.type="button",o.className="btn btn-xs btn-ghost",o.textContent=d?"\u25BE":"\u25B8";const i=document.createElement("div");i.className="fb-logic-body",i.style.cssText="padding:.25rem 0 .25rem;"+(d?"":"display:none;"),a.addEventListener("click",()=>{const p=i.style.display==="none";i.style.display=p?"":"none",o.textContent=p?"\u25BE":"\u25B8"}),a.appendChild(l),a.appendChild(o),s.appendChild(a),i.appendChild(Z(c.visibility||{},t,n)),i.appendChild($(c.requirement||{},t,n)),i.appendChild(ee(c.validation||[],t,n));const m=document.getElementById(`fb-type-${t}`)?.value||e.type;return I.has(m)&&i.appendChild(te(c.cascade||{},t,n)),s.appendChild(i),s}function j(e,t,c){const n=document.createElement("div");return n.className="fb-field-extras",I.has(e)&&n.appendChild(ae(t.options||[],c)),e==="textarea"&&n.appendChild(k([w("Rows",`fb-rows-${c}`,"number",t.formConfig?.rows||4,"Height of textarea")])),(e==="string"||e==="textarea")&&n.appendChild(k([w("Min Length",`fb-minlength-${c}`,"number",t.minLength||"",""),w("Max Length",`fb-maxlength-${c}`,"number",t.maxLength||"","")])),e==="number"&&n.appendChild(k([w("Min",`fb-min-${c}`,"number",t.min??"",""),w("Max",`fb-max-${c}`,"number",t.max??"","")])),n.children.length?n:null}function k(e){const t=document.createElement("div");return t.style.cssText="display:flex;gap:.75rem;margin-bottom:.6rem;",e.forEach(c=>{c&&t.appendChild(c)}),t}function w(e,t,c,n,s){const a=document.createElement("div");a.style.flex="1";const l=document.createElement("label");l.htmlFor=t,l.className="form-label",l.textContent=e,l.style.fontSize=".8rem";const d=document.createElement("input");if(d.id=t,d.type=c||"text",d.className="form-input",d.value=n??"",a.appendChild(l),a.appendChild(d),s){const o=document.createElement("p");o.className="form-hint text-muted",o.textContent=s,o.style.cssText="font-size:.73rem;margin-top:.2rem;",a.appendChild(o)}return a}function le(e,t,c,n){const s=document.createElement("div");s.style.flex="1";const a=document.createElement("label");a.htmlFor=t,a.className="form-label",a.textContent=e,a.style.fontSize=".8rem";const l=document.createElement("select");return l.id=t,l.className="form-input",c.forEach(d=>{const o=document.createElement("option");o.value=d.value,o.textContent=d.label,d.value===n&&(o.selected=!0),l.appendChild(o)}),s.appendChild(a),s.appendChild(l),s}function D(e,t,c){const n=document.createElement("div");n.style.cssText="flex:0;min-width:80px;display:flex;flex-direction:column;justify-content:flex-end;";const s=document.createElement("label");s.style.cssText="display:flex;align-items:center;gap:.4rem;cursor:pointer;font-size:.8rem;white-space:nowrap;";const a=document.createElement("input");return a.id=t,a.type="checkbox",a.checked=c,s.appendChild(a),s.appendChild(document.createTextNode(e)),n.appendChild(s),n}function ae(e,t){const c=document.createElement("div");c.style.cssText="margin-top:.4rem;";const n=document.createElement("p");n.textContent="Options (one per line: value or value:Label)",n.style.cssText="font-size:.8rem;font-weight:600;margin-bottom:.3rem;";const s=document.createElement("textarea");return s.id=`fb-options-${t}`,s.className="form-input",s.rows=4,s.placeholder=`yes:Yes
|
|
6
|
+
no:No
|
|
7
|
+
maybe:Maybe`,s.value=(e||[]).map(a=>{const l=typeof a=="string"?a:a.value??"",d=typeof a=="string"?a:a.label??l;return l===d?l:`${l}:${d}`}).join(`
|
|
8
|
+
`),s.style.fontFamily="monospace",c.appendChild(n),c.appendChild(s),c}function V(e){return v(),{title:e.find("#field-title").val().trim(),slug:e.find("#field-slug").val().trim(),description:e.find("#field-description").val().trim(),fields:b,settings:{submitText:e.find("#setting-submit-text").val().trim()||"Submit",successMessage:e.find("#setting-success-message").val().trim()||"Thank you.",layout:e.find("#setting-layout").val()||"stacked",columns:parseInt(e.find("#setting-columns").val(),10)||2,submitSpan:e.find("#setting-submit-span").val()||"",honeypot:e.find("#setting-honeypot").prop("checked"),rateLimitPerMinute:parseInt(e.find("#setting-rate-limit").val(),10)||3,successRedirect:e.find("#setting-success-redirect").val().trim()||null,actionSlug:e.find("#action-cms-slug").val()||null},actions:{email:{enabled:e.find("#action-email-enabled").prop("checked"),recipients:e.find("#action-email-recipients").val().trim(),subjectPrefix:e.find("#action-email-subject-prefix").val().trim()},webhook:{enabled:e.find("#action-webhook-enabled").prop("checked"),url:e.find("#action-webhook-url").val().trim(),method:e.find("#action-webhook-method").val()},...L!==null&&{collection:L}}}}function se(e){const t=[];let c=[],n="Step 1",s="";return e.forEach(a=>{a.type==="page-break"?(t.push({title:n,description:s,fields:c}),c=[],n=a.label||`Step ${t.length+1}`,s=a.description||""):a.type!=="spacer"&&c.push(a)}),(c.length||t.length===0)&&t.push({title:n,description:s,fields:c}),t}function H(e,t){const c={};return e.forEach(n=>{if(n.type==="page-break"||n.type==="spacer")return;const s={...n.formConfig||{}};s.span==="full"&&t&&(s.span=t);const a=n.type==="checkbox"?"boolean":n.type==="date"?"string":n.type;c[n.name]={type:a,label:n.label,required:n.required,options:n.options,formConfig:{...n.placeholder&&{placeholder:n.placeholder},...n.helper&&{hint:n.helper},...s}}}),c}function U(e,t){const c=typeof e=="string"?document.querySelector(e):e;c&&(t||[]).forEach(n=>{if(n.type!=="date"||!n.name)return;const s=c.querySelector(`[name="${n.name}"]`);s&&s.type!=="date"&&(s.type="date")})}export const formEditorView={templateUrl:"/admin/js/templates/form-editor.html",async onMount(e){b=[],C=null,L=null;const c=window.location.hash.match(/\/forms\/edit\/([^/?#]+)/);C=c?c[1]:null;let n=null;if(C)try{n=await T(`/forms/${C}`),b=n.fields||[],L=n.actions?.collection??null}catch{E.toast("Could not load form.",{type:"error"})}if(n?e.find("#editor-title").get(0).textContent=`Edit: ${n.title}`:e.find("#editor-title").get(0).textContent="New Form",C||e.find("#field-title").get(0).addEventListener("input",function(){e.find("#field-slug").val(O(this.value))}),E.tabs(e.find("#editor-tabs").get(0)),n){e.find("#field-title").val(n.title),e.find("#field-slug").val(n.slug),e.find("#field-description").val(n.description||"");const l=n.settings||{};e.find("#setting-submit-text").val(l.submitText||"Submit"),e.find("#setting-success-message").val(l.successMessage||""),e.find("#setting-layout").val(l.layout||"stacked"),e.find("#setting-columns").val(l.columns||2),e.find("#setting-submit-span").val(l.submitSpan||""),e.find("#columns-group").get(0).style.display=l.layout==="grid"?"":"none",e.find("#setting-honeypot").prop("checked",l.honeypot!==!1),e.find("#setting-rate-limit").val(l.rateLimitPerMinute||3),e.find("#setting-success-redirect").val(l.successRedirect||"");const d=n.actions?.email||{};e.find("#action-email-enabled").prop("checked",d.enabled||!1),e.find("#action-email-recipients").val(d.recipients||""),e.find("#action-email-subject-prefix").val(d.subjectPrefix||"");const o=n.actions?.webhook||{};e.find("#action-webhook-enabled").prop("checked",o.enabled||!1),e.find("#action-webhook-url").val(o.url||""),e.find("#action-webhook-method").val(o.method||"POST")}const s=n?.settings?.actionSlug||"";try{const l=await T("/actions").catch(()=>[]),d=e.find("#action-cms-slug").get(0);if((Array.isArray(l)?l:[]).forEach(o=>{const i=document.createElement("option");i.value=o.slug,i.textContent=o.title||o.slug,o.slug===s&&(i.selected=!0),d.appendChild(i)}),!l.length){const o=document.createElement("option");o.value="",o.textContent="No actions available",o.disabled=!0,d.appendChild(o)}}catch{}e.find("#setting-layout").get(0)?.addEventListener("change",function(){const l=this.value==="grid";e.find("#columns-group").get(0).style.display=l?"":"none",document.querySelectorAll(".fb-grid-row").forEach(d=>{d.style.display=l?"flex":"none"})}),x(e);const a=e.find("#add-element-menu").get(0);e.find("#add-element-btn").get(0).addEventListener("click",l=>{l.stopPropagation(),a.style.display=a.style.display==="none"?"":"none"}),q&&document.removeEventListener("click",q),q=()=>{a&&(a.style.display="none")},document.addEventListener("click",q),e.find("#add-field-btn").get(0).addEventListener("click",()=>{a&&(a.style.display="none"),v();const l=b.length;b.push({name:`field_${l+1}`,type:"string",label:"New Field",required:!1,placeholder:""}),x(e);const o=e.find("#fields-list").get(0)?.lastElementChild;if(o){const i=o.querySelector(".fb-field-body");i&&(i.style.display="")}}),e.find("#add-spacer-btn").get(0).addEventListener("click",()=>{a&&(a.style.display="none"),v(),b.push({type:"spacer"}),x(e)}),e.find("#add-page-break-btn").get(0).addEventListener("click",()=>{a&&(a.style.display="none"),v();const l=b.filter(d=>d.type==="page-break").length+2;b.push({type:"page-break",label:`Step ${l}`,description:""}),x(e)}),e.find("#save-form-btn").get(0).addEventListener("click",async()=>{const l=V(e);if(!l.title){E.toast("Please enter a form title.",{type:"error"});return}try{C?(await T(`/forms/${C}`,{method:"PUT",body:JSON.stringify(l)}),E.toast("Form saved.",{type:"success"})):(C=(await T("/forms",{method:"POST",body:JSON.stringify(l)})).slug,R.navigate(`/forms/edit/${C}`),E.toast("Form created.",{type:"success"}))}catch(d){E.toast(d.message||"Failed to save form.",{type:"error"})}}),e.find("#preview-btn").get(0).addEventListener("click",()=>{const l=V(e),d=e.find("#preview-container").get(0);if(!d)return;const o=e.find("#preview-test-result").get(0),i=e.find("#preview-test-badge").get(0);o&&(o.style.display="none",o.textContent=""),i&&(i.style.display=C?"":"none"),e.find("#preview-card").get(0).style.display="",d.textContent="";const m=document.createElement("div");m.id="fb-preview-form",d.appendChild(m);const p=C?async r=>{o&&(o.style.display="none",o.textContent="");try{const u=await fetch(`/api/forms/submit/${C}`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(r)}),g=await u.json();if(!u.ok)throw new Error(g.error||"Submission failed.");o&&(o.textContent=g.message||l.settings?.successMessage||"Submitted successfully.",o.style.cssText="display:block;margin-top:.75rem;padding:.6rem .9rem;border-radius:6px;font-size:.9rem;background:rgba(34,197,94,.12);color:var(--success,#22c55e);"),E.toast("Test submission stored.",{type:"success"})}catch(u){o&&(o.textContent=u.message,o.style.cssText="display:block;margin-top:.75rem;padding:.6rem .9rem;border-radius:6px;font-size:.9rem;background:rgba(239,68,68,.12);color:var(--danger,#ef4444);"),E.toast(u.message,{type:"error"})}return!1}:()=>!1,f=l.fields.some(r=>r.type==="page-break");if(typeof F<"u"){const r=l.settings?.columns||2,u=l.settings?.layout||"stacked";if(f&&F.wizard){const g=se(l.fields).map(h=>({title:h.title,description:h.description,fields:H(h.fields,r)}));F.wizard("#fb-preview-form",{schema:{steps:g},onSubmit:p}),U("#fb-preview-form",l.fields)}else if(F.render){const g=H(l.fields,r),h={};if(l.fields.forEach(y=>{if(!(!y.name||y.type==="page-break"||y.type==="spacer")&&(y.type==="select"||y.type==="multiselect")&&y.required){const S=(y.options||[])[0];S&&(h[y.name]=typeof S=="object"?S.value:S)}}),F.render("#fb-preview-form",g,h,{submitText:l.settings?.submitText||"Submit",layout:u,columns:r,onSubmit:p}),u==="grid"&&l.settings?.submitSpan==="full"){const y=document.querySelector("#fb-preview-form .form-buttons");y&&y.classList.add("col-span-full")}U("#fb-preview-form",l.fields)}window.FormLogicEngine&&l.fields.some(g=>g.logic)&&requestAnimationFrame(()=>{new window.FormLogicEngine.FormLogicRuntime({fields:l.fields},m).init()})}else{const r=document.createElement("p");r.textContent=`${l.fields.filter(u=>u.type!=="page-break").length} field(s): ${l.fields.filter(u=>u.type!=="page-break").map(u=>u.label).join(", ")}`,r.style.cssText="color:var(--muted);font-style:italic;",m.appendChild(r)}e.find("#preview-card").get(0).scrollIntoView({behavior:"smooth",block:"start"})})}};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{apiRequest as x}from"/admin/js/api.js";let c=null,w=[],i=[];export const formSubmissionsView={templateUrl:"/admin/js/templates/form-submissions.html",async onMount(s){const r=window.location.hash.match(/\/forms\/([^/?#]+)\/submissions/);if(c=r?r[1]:null,!c){E.toast("No form selected.",{type:"error"});return}try{const e=await x(`/forms/${c}`);w=(e.fields||[]).filter(n=>n.type!=="page-break"),s.find("#submissions-title").get(0).textContent=`${e.title} \u2014 Submissions`}catch{E.toast("Could not load form definition.",{type:"error"})}await L(s),s.find("#export-btn").off("click").on("click",()=>{const e=E.slideover({title:"Export Submissions",size:"sm",position:"right"}),n=document.createElement("div");n.style.cssText="padding:1.25rem;display:flex;flex-direction:column;gap:1rem;";function a(l,u,f,y){const C=document.createElement("div");C.style.cssText="display:flex;flex-direction:column;gap:.4rem;";const b=document.createElement("button");b.className="btn btn-ghost",b.style.cssText="justify-content:flex-start;gap:.5rem;";const k=document.createElement("span");return k.setAttribute("data-icon",u),b.appendChild(k),b.appendChild(document.createTextNode(" "+l)),b.addEventListener("click",()=>{fetch(f,{headers:{Authorization:"Bearer "+(S.get("auth_token")||"")}}).then(v=>v.blob()).then(v=>{const h=document.createElement("a");h.href=URL.createObjectURL(v),h.download=y,document.body.appendChild(h),h.click(),document.body.removeChild(h),URL.revokeObjectURL(h.href),e.close()}).catch(()=>E.toast("Export failed.",{type:"error"}))}),C.appendChild(b),C}n.appendChild(a("Export as CSV","file-text",`/api/forms/${c}/submissions/export`,`${c}-submissions.csv`)),n.appendChild(a("Export as JSON","code",`/api/forms/${c}/submissions/export/json`,`${c}-submissions.json`)),e.setContent(n),Domma.icons.scan(n),e.open()}),s.find("#clear-all-btn").off("click").on("click",async()=>{if(await E.confirm("Delete all submissions? This cannot be undone."))try{await x(`/forms/${c}/submissions`,{method:"DELETE"}),i=[],E.toast("All submissions cleared.",{type:"success"}),g([],s)}catch{E.toast("Failed to clear submissions.",{type:"error"})}});const d=s.find("#sub-search").get(0),m=s.find("#sub-date-from").get(0),o=s.find("#sub-date-to").get(0);function t(){const e=d?.value.toLowerCase()||"",n=m?.value?new Date(m.value):null,a=o?.value?new Date(o.value+"T23:59:59"):null,l=i.filter(u=>{if(e&&!Object.values(u.data||{}).map(y=>String(y).toLowerCase()).some(y=>y.includes(e)))return!1;if(n||a){const f=u.meta?.createdAt?new Date(u.meta.createdAt):null;if(!f||n&&f<n||a&&f>a)return!1}return!0});g(l,s)}d&&d.addEventListener("input",t),m&&m.addEventListener("change",t),o&&o.addEventListener("change",t),Domma.icons.scan()}};async function L(s){try{i=await x(`/forms/${c}/submissions`)}catch{i=[],E.toast("Could not load submissions.",{type:"error"})}g(i,s)}function g(s,p){const r=p.find("#sub-count").get(0);r&&(s.length===i.length?r.textContent=`${i.length} submission${i.length!==1?"s":""}`:r.textContent=`Showing ${s.length} of ${i.length}`);const m=[...w.map(t=>({key:`data.${t.name}`,title:t.label||t.name,render:(e,n)=>{const a=n.data?.[t.name]??"",l=String(a),u=document.createElement("span");return u.title=l,u.textContent=l.length>80?l.slice(0,80)+"\u2026":l,u.outerHTML}})),{key:"meta",title:"Date",render:t=>D(t?.createdAt).format("DD MMM YYYY HH:mm")},{key:"id",title:"",render:t=>{const e=document.createElement("button");return e.className="btn btn-sm btn-danger js-delete-submission",e.dataset.id=t,e.textContent="Delete",e.style.whiteSpace="nowrap",e.outerHTML}}];T.create("#submissions-table",{data:s,columns:m,emptyMessage:"No submissions yet."});const o=document.querySelector("#submissions-table");o&&(o.querySelectorAll(".js-delete-submission").forEach(t=>{t.addEventListener("click",async e=>{e.stopPropagation();const n=t.dataset.id;if(await E.confirm("Delete this submission?"))try{await x(`/forms/${c}/submissions/${n}`,{method:"DELETE"}),i=i.filter(l=>l.id!==n),E.toast("Submission deleted.",{type:"success"}),g(i,p)}catch{E.toast("Failed to delete submission.",{type:"error"})}})}),o.querySelectorAll("tbody tr").forEach((t,e)=>{const n=s[e];n&&(t.style.cursor="pointer",t.addEventListener("click",a=>{a.target.closest(".js-delete-submission")||M(n)}))}))}function M(s){const p=document.createElement("div"),r=document.createElement("div");r.style.cssText="display:flex;flex-direction:column;gap:.75rem;margin-bottom:1.25rem;",w.forEach(o=>{const t=s.data?.[o.name];if(t==null||t==="")return;const e=document.createElement("div");e.style.cssText="border-bottom:1px solid var(--border-color,#333);padding-bottom:.6rem;";const n=document.createElement("strong");n.textContent=o.label||o.name,n.style.cssText="display:block;font-size:.8rem;color:var(--text-muted,#888);margin-bottom:.2rem;";const a=document.createElement("p");a.textContent=String(t),a.style.cssText="margin:0;word-break:break-word;",e.appendChild(n),e.appendChild(a),r.appendChild(e)});const d=document.createElement("div");if(d.style.cssText="font-size:.8rem;color:var(--text-muted,#888);display:flex;flex-direction:column;gap:.3rem;border-top:1px solid var(--border-color,#333);padding-top:.75rem;",s.meta?.createdAt){const o=document.createElement("span");o.textContent=`Submitted: ${D(s.meta.createdAt).format("DD MMM YYYY HH:mm")}`,d.appendChild(o)}if(s.meta?.ip){const o=document.createElement("span");o.textContent=`IP: ${s.meta.ip}`,d.appendChild(o)}p.appendChild(r),p.appendChild(d);const m=E.modal({title:"Submission Details",size:"md"});m.element.appendChild(p),m.open()}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{apiRequest as p}from"/admin/js/api.js";function u(i){return String(i).replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""").replace(/'/g,"'")}export const formsView={templateUrl:"/admin/js/templates/forms.html",async onMount(i){await y(i),i.find("#create-form-btn").off("click").on("click",()=>{const c=E.modal({title:"Create Form",size:"sm"}),t=document.createElement("div");t.style.cssText="padding:.25rem 0 .5rem;";const e=document.createElement("div");F.create({title:{type:"string",label:"Form Title",placeholder:"e.g. Contact, Feedback\u2026",required:!0}},{},{showSubmitButton:!1}).renderTo(e),t.appendChild(e);const n=document.createElement("div");n.style.cssText="display:flex;justify-content:flex-end;gap:.5rem;margin-top:.75rem;";const s=document.createElement("button");s.className="btn btn-ghost",s.textContent="Cancel";const o=document.createElement("button");o.className="btn btn-primary",o.textContent="Create",n.appendChild(s),n.appendChild(o),t.appendChild(n),c.element.appendChild(t),c.open();const a=e.querySelector('input[name="title"]');setTimeout(()=>a?.focus(),50);async function m(){const r=a?.value.trim();if(r)try{const l=await p("/forms",{method:"POST",body:JSON.stringify({title:r})});c.close(),R.navigate(`/forms/edit/${l.slug}`)}catch(l){E.toast(l.message||"Failed to create form.",{type:"error"})}}s.addEventListener("click",()=>c.close()),o.addEventListener("click",m),a?.addEventListener("keydown",r=>{r.key==="Enter"&&m()})}),i.find("#test-email-btn").off("click").on("click",async()=>{const t=(window.__CMS_SITE__?.smtp||{}).fromAddress||"",e=E.modal({title:"Send Test Email",size:"sm"}),n=document.createElement("div");n.style.cssText="padding:.25rem 0 .5rem;";const s=document.createElement("div");s.className="mb-3";const o=document.createElement("label");o.className="form-label",o.textContent="Send to";const a=document.createElement("input");a.type="email",a.className="form-input",a.value=t,a.placeholder="test@example.com",s.appendChild(o),s.appendChild(a),n.appendChild(s);const m=document.createElement("p");m.className="text-muted",m.style.cssText="font-size:.8rem;margin-bottom:.75rem;",m.textContent="SMTP settings are configured in Site Settings.",n.appendChild(m);const r=document.createElement("div");r.style.cssText="display:flex;justify-content:flex-end;gap:.5rem;margin-top:.75rem;";const l=document.createElement("button");l.className="btn btn-ghost",l.textContent="Cancel";const d=document.createElement("button");d.className="btn btn-primary",d.textContent="Send",r.appendChild(l),r.appendChild(d),n.appendChild(r),e.element.appendChild(n),e.open(),setTimeout(()=>a?.focus(),50),l.addEventListener("click",()=>e.close()),d.addEventListener("click",async()=>{const f=a.value.trim();if(f){d.disabled=!0;try{await p("/forms/test-email",{method:"POST",body:JSON.stringify({to:f})}),e.close(),E.toast("Test email sent.",{type:"success"})}catch(b){E.toast(b.message||"Failed to send test email.",{type:"error"})}finally{d.disabled=!1}}})}),Domma.icons.scan()}};async function y(i){let c=[];try{c=await p("/forms")}catch{E.toast("Could not load forms.",{type:"error"})}T.create("#forms-table",{data:c,columns:[{key:"title",title:"Title",render:(t,e)=>{const n=document.createElement("a");return n.href=`#/forms/edit/${u(e.slug)}`,n.textContent=t,n.style.fontWeight="600",n.outerHTML}},{key:"slug",title:"Slug",render:t=>{const e=document.createElement("code");return e.textContent=t,e.outerHTML}},{key:"fields",title:"Field Count",render:t=>String(t?.length??0)},{key:"submissionCount",title:"Submission Count",render:t=>String(t??0)},{key:"slug",title:"Actions",render:t=>{const e=document.createElement("div");e.style.cssText="display:flex;gap:.4rem;justify-content:flex-end;";const n=document.createElement("a");n.href=`#/forms/edit/${u(t)}`,n.className="btn btn-sm btn-primary",n.textContent="Edit";const s=document.createElement("a");s.href=`#/forms/${u(t)}/submissions`,s.className="btn btn-sm btn-ghost",s.textContent="Submissions";const o=document.createElement("button");return o.className="btn btn-sm btn-danger js-delete-form",o.dataset.slug=t,o.textContent="Delete",e.appendChild(n),e.appendChild(s),e.appendChild(o),e.outerHTML}}],emptyMessage:'No forms yet. Click "Create Form" to get started.'}),document.querySelectorAll(".js-delete-form").forEach(t=>{t.addEventListener("click",async()=>{const e=t.dataset.slug;if(await E.confirm(`Delete form "${e}" and all its submissions? This cannot be undone.`))try{await p(`/forms/${e}`,{method:"DELETE"}),E.toast("Form deleted.",{type:"success"}),await y(i)}catch{E.toast("Failed to delete form.",{type:"error"})}})}),Domma.icons.scan()}
|
package/admin/js/views/index.js
CHANGED
|
@@ -1,39 +1 @@
|
|
|
1
|
-
|
|
2
|
-
* View Registry
|
|
3
|
-
* Maps route view names to their view modules.
|
|
4
|
-
*/
|
|
5
|
-
import {dashboardView} from './dashboard.js';
|
|
6
|
-
import {pagesView} from './pages.js';
|
|
7
|
-
import {pageEditorView} from './page-editor.js';
|
|
8
|
-
import {settingsView} from './settings.js';
|
|
9
|
-
import {navigationView} from './navigation.js';
|
|
10
|
-
import {layoutsView} from './layouts.js';
|
|
11
|
-
import {mediaView} from './media.js';
|
|
12
|
-
import {loginView} from './login.js';
|
|
13
|
-
import {usersView} from './users.js';
|
|
14
|
-
import {userEditorView} from './user-editor.js';
|
|
15
|
-
import {pluginsView} from './plugins.js';
|
|
16
|
-
import {documentationView} from './documentation.js';
|
|
17
|
-
import {tutorialsView} from './tutorials.js';
|
|
18
|
-
import {collectionsView} from './collections.js';
|
|
19
|
-
import {collectionEditorView} from './collection-editor.js';
|
|
20
|
-
import {collectionEntriesView} from './collection-entries.js';
|
|
21
|
-
|
|
22
|
-
export const views = {
|
|
23
|
-
dashboard: dashboardView,
|
|
24
|
-
pages: pagesView,
|
|
25
|
-
pageEditor: pageEditorView,
|
|
26
|
-
settings: settingsView,
|
|
27
|
-
navigation: navigationView,
|
|
28
|
-
layouts: layoutsView,
|
|
29
|
-
media: mediaView,
|
|
30
|
-
login: loginView,
|
|
31
|
-
users: usersView,
|
|
32
|
-
userEditor: userEditorView,
|
|
33
|
-
plugins: pluginsView,
|
|
34
|
-
documentation: documentationView,
|
|
35
|
-
tutorials: tutorialsView,
|
|
36
|
-
collections: collectionsView,
|
|
37
|
-
collectionEditor: collectionEditorView,
|
|
38
|
-
collectionEntries: collectionEntriesView
|
|
39
|
-
};
|
|
1
|
+
import{dashboardView as o}from"./dashboard.js";import{pagesView as i}from"./pages.js";import{pageEditorView as r}from"./page-editor.js";import{settingsView as t}from"./settings.js";import{navigationView as e}from"./navigation.js";import{layoutsView as m}from"./layouts.js";import{mediaView as s}from"./media.js";import{loginView as p}from"./login.js";import{usersView as f}from"./users.js";import{userEditorView as w}from"./user-editor.js";import{pluginsView as n}from"./plugins.js";import{documentationView as V}from"./documentation.js";import{tutorialsView as l}from"./tutorials.js";import{apiReferenceView as c}from"./api-reference.js";import{collectionsView as a}from"./collections.js";import{collectionEditorView as d}from"./collection-editor.js";import{collectionEntriesView as E}from"./collection-entries.js";import{formsView as u}from"./forms.js";import{formEditorView as g}from"./form-editor.js";import{formSubmissionsView as v}from"./form-submissions.js";import{viewsListView as b}from"./views-list.js";import{viewEditorView as k}from"./view-editor.js";import{viewPreviewView as y}from"./view-preview.js";import{actionsListView as L}from"./actions-list.js";import{actionEditorView as P}from"./action-editor.js";import{proDocsView as h}from"./pro-docs.js";import{blocksView as D}from"./blocks.js";import{blockEditorView as R}from"./block-editor.js";import{myProfileView as S}from"./my-profile.js";import{rolesView as x}from"./roles.js";import{roleEditorView as j}from"./role-editor.js";export const views={dashboard:o,pages:i,pageEditor:r,settings:t,navigation:e,layouts:m,media:s,login:p,users:f,userEditor:w,plugins:n,documentation:V,tutorials:l,apiReference:c,collections:a,collectionEditor:d,collectionEntries:E,forms:u,formEditor:g,formSubmissions:v,viewsList:b,viewEditor:k,viewPreview:y,actionsList:L,actionEditor:P,proDocs:h,blocks:D,blockEditor:R,myProfile:S,roles:x,roleEditor:j};
|
|
@@ -1,49 +1,16 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
*/
|
|
4
|
-
import { api } from '../api.js';
|
|
5
|
-
|
|
6
|
-
export const layoutsView = {
|
|
7
|
-
templateUrl: '/admin/js/templates/layouts.html',
|
|
8
|
-
|
|
9
|
-
async onMount($container) {
|
|
10
|
-
let presets = await api.layouts.get().catch(() => ({}));
|
|
11
|
-
const $grid = $container.find('#presets-grid').empty();
|
|
12
|
-
|
|
13
|
-
Object.entries(presets).forEach(([key, preset]) => {
|
|
14
|
-
$grid.append(`
|
|
15
|
-
<div class="preset-card card" data-key="${key}">
|
|
1
|
+
import{api as c}from"../api.js";export const layoutsView={templateUrl:"/admin/js/templates/layouts.html",async onMount(a){E.tabs(a.find("#layouts-tabs").get(0));let t=await c.layouts.get().catch(()=>({}));const r=a.find("#presets-grid").empty();Object.entries(t).forEach(([s,e])=>{r.append(`
|
|
2
|
+
<div class="preset-card card" data-key="${s}">
|
|
16
3
|
<div class="card-header">
|
|
17
|
-
<h3>${
|
|
18
|
-
<span class="badge">${
|
|
4
|
+
<h3>${e.label||s}</h3>
|
|
5
|
+
<span class="badge">${s}</span>
|
|
19
6
|
</div>
|
|
20
7
|
<div class="card-body">
|
|
21
|
-
<p class="text-muted">${
|
|
8
|
+
<p class="text-muted">${e.description||""}</p>
|
|
22
9
|
<div class="preset-toggles">
|
|
23
|
-
<label class="toggle-label"><input type="checkbox" class="form-check preset-navbar" ${
|
|
24
|
-
<label class="toggle-label"><input type="checkbox" class="form-check preset-footer" ${
|
|
25
|
-
<label class="toggle-label"><input type="checkbox" class="form-check preset-sidebar" ${
|
|
10
|
+
<label class="toggle-label"><input type="checkbox" class="form-check preset-navbar" ${e.navbar?"checked":""}> Navbar</label>
|
|
11
|
+
<label class="toggle-label"><input type="checkbox" class="form-check preset-footer" ${e.footer?"checked":""}> Footer</label>
|
|
12
|
+
<label class="toggle-label"><input type="checkbox" class="form-check preset-sidebar" ${e.sidebar?"checked":""}> Sidebar</label>
|
|
26
13
|
</div>
|
|
27
14
|
</div>
|
|
28
15
|
</div>
|
|
29
|
-
`);
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
$container.find('#save-layouts-btn').on('click', async () => {
|
|
33
|
-
$container.find('.preset-card').each(function () {
|
|
34
|
-
const key = $(this).data('key');
|
|
35
|
-
if (presets[key]) {
|
|
36
|
-
presets[key].navbar = $(this).find('.preset-navbar').is(':checked');
|
|
37
|
-
presets[key].footer = $(this).find('.preset-footer').is(':checked');
|
|
38
|
-
presets[key].sidebar = $(this).find('.preset-sidebar').is(':checked');
|
|
39
|
-
}
|
|
40
|
-
});
|
|
41
|
-
try {
|
|
42
|
-
await api.layouts.save(presets);
|
|
43
|
-
E.toast('Layouts saved.', { type: 'success' });
|
|
44
|
-
} catch {
|
|
45
|
-
E.toast('Failed to save layouts.', { type: 'error' });
|
|
46
|
-
}
|
|
47
|
-
});
|
|
48
|
-
}
|
|
49
|
-
};
|
|
16
|
+
`)}),a.find("#save-layouts-btn").on("click",async()=>{a.find(".preset-card").each(function(){const s=$(this).data("key");t[s]&&(t[s].navbar=$(this).find(".preset-navbar").is(":checked"),t[s].footer=$(this).find(".preset-footer").is(":checked"),t[s].sidebar=$(this).find(".preset-sidebar").is(":checked"))});try{await c.layouts.save(t),E.toast("Layouts saved.",{type:"success"})}catch{E.toast("Failed to save layouts.",{type:"error"})}});const l=await c.layouts.getOptions().catch(()=>({spacerSize:8,spacerClass:""})),o=a.find("#spacer-size-input"),i=a.find("#spacer-class-input");o.val(l.spacerSize??8),i.val(l.spacerClass??""),a.find("#save-options-btn").on("click",async()=>{const s=parseInt(o.val(),10)||8,e=i.val().trim();try{await c.layouts.saveOptions({spacerSize:s,spacerClass:e}),E.toast("Layout options saved.",{type:"success"})}catch{E.toast("Failed to save layout options.",{type:"error"})}})}};
|