domma-cms 0.1.0 → 0.2.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/admin/css/admin.css +78 -1
- package/admin/js/api.js +32 -0
- package/admin/js/app.js +24 -7
- package/admin/js/config/sidebar-config.js +8 -0
- package/admin/js/templates/collection-editor.html +80 -0
- package/admin/js/templates/collection-entries.html +36 -0
- package/admin/js/templates/collections.html +12 -0
- package/admin/js/templates/documentation.html +136 -0
- package/admin/js/templates/navigation.html +26 -4
- package/admin/js/templates/page-editor.html +91 -85
- package/admin/js/templates/settings.html +433 -172
- package/admin/js/views/collection-editor.js +487 -0
- package/admin/js/views/collection-entries.js +484 -0
- package/admin/js/views/collections.js +153 -0
- package/admin/js/views/dashboard.js +14 -6
- package/admin/js/views/index.js +9 -3
- package/admin/js/views/login.js +3 -2
- package/admin/js/views/navigation.js +77 -11
- package/admin/js/views/page-editor.js +207 -25
- package/admin/js/views/pages.js +14 -6
- package/admin/js/views/settings.js +137 -2
- package/admin/js/views/users.js +10 -7
- package/bin/cli.js +37 -10
- package/config/auth.json +2 -1
- package/config/content.json +1 -0
- package/config/navigation.json +14 -4
- package/config/plugins.json +0 -18
- package/config/presets.json +4 -8
- package/config/site.json +44 -3
- package/package.json +6 -2
- package/plugins/domma-effects/admin/templates/domma-effects.html +92 -3
- package/plugins/domma-effects/plugin.js +125 -0
- package/plugins/domma-effects/public/inject-body.html +19 -0
- package/plugins/example-analytics/admin/views/analytics.js +2 -2
- package/plugins/example-analytics/plugin.json +8 -0
- package/plugins/example-analytics/stats.json +15 -1
- package/plugins/form-builder/admin/templates/form-editor.html +19 -6
- package/plugins/form-builder/admin/views/form-editor.js +634 -9
- package/plugins/form-builder/admin/views/form-submissions.js +4 -4
- package/plugins/form-builder/admin/views/forms-list.js +5 -5
- package/plugins/form-builder/data/forms/consent.json +104 -0
- package/plugins/form-builder/data/forms/contacts.json +66 -0
- package/plugins/form-builder/data/submissions/consent.json +13 -0
- package/plugins/form-builder/data/submissions/contacts.json +26 -0
- package/plugins/form-builder/plugin.js +62 -11
- package/plugins/form-builder/plugin.json +12 -16
- package/plugins/form-builder/public/form-logic-engine.js +568 -0
- package/plugins/form-builder/public/inject-body.html +88 -6
- package/plugins/form-builder/public/inject-head.html +16 -0
- package/plugins/form-builder/public/package.json +1 -0
- package/public/css/site.css +113 -0
- package/public/js/btt.js +90 -0
- package/public/js/cookie-consent.js +61 -0
- package/public/js/site.js +129 -34
- package/scripts/build.js +129 -0
- package/scripts/seed.js +517 -7
- package/server/routes/api/collections.js +301 -0
- package/server/routes/api/settings.js +66 -2
- package/server/server.js +19 -15
- package/server/services/collections.js +430 -0
- package/server/services/content.js +11 -2
- package/server/services/hooks.js +109 -0
- package/server/services/markdown.js +500 -149
- package/server/services/plugins.js +6 -1
- package/server/services/renderer.js +73 -7
- package/server/templates/page.html +38 -3
- package/plugins/back-to-top/admin/templates/back-to-top-settings.html +0 -55
- package/plugins/back-to-top/admin/views/back-to-top-settings.js +0 -44
- package/plugins/back-to-top/config.js +0 -10
- package/plugins/back-to-top/plugin.js +0 -24
- package/plugins/back-to-top/plugin.json +0 -36
- package/plugins/back-to-top/public/inject-body.html +0 -105
- package/plugins/cookie-consent/admin/templates/cookie-consent-settings.html +0 -113
- package/plugins/cookie-consent/admin/views/cookie-consent-settings.js +0 -73
- package/plugins/cookie-consent/config.js +0 -30
- package/plugins/cookie-consent/plugin.js +0 -24
- package/plugins/cookie-consent/plugin.json +0 -36
- package/plugins/cookie-consent/public/inject-body.html +0 -69
- package/plugins/custom-css/admin/templates/custom-css.html +0 -17
- package/plugins/custom-css/admin/views/custom-css.js +0 -35
- package/plugins/custom-css/config.js +0 -1
- package/plugins/custom-css/data/custom.css +0 -0
- package/plugins/custom-css/plugin.js +0 -63
- package/plugins/custom-css/plugin.json +0 -32
- package/plugins/custom-css/public/inject-head.html +0 -1
- package/plugins/form-builder/data/forms/contact.json +0 -52
- package/plugins/form-builder/data/submissions/contact.json +0 -14
package/admin/css/admin.css
CHANGED
|
@@ -108,11 +108,52 @@
|
|
|
108
108
|
margin-top: .25rem;
|
|
109
109
|
}
|
|
110
110
|
|
|
111
|
+
/* ─── Page editor: lock to viewport, no outer scroll ─────────────────────── */
|
|
112
|
+
.dashboard-main:has(#editor-meta-tabs) {
|
|
113
|
+
overflow: hidden;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
.view-container:has(#editor-meta-tabs) {
|
|
117
|
+
display: flex;
|
|
118
|
+
flex-direction: column;
|
|
119
|
+
height: calc(100dvh - 60px); /* 60px = topbar */
|
|
120
|
+
padding-bottom: 0;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
#editor-meta-tabs {
|
|
124
|
+
flex: 1;
|
|
125
|
+
min-height: 0;
|
|
126
|
+
display: flex;
|
|
127
|
+
flex-direction: column;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
#editor-meta-tabs .tab-content {
|
|
131
|
+
flex: 1;
|
|
132
|
+
min-height: 0;
|
|
133
|
+
overflow: hidden;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
#editor-meta-tabs .tab-panel {
|
|
137
|
+
height: 100%;
|
|
138
|
+
overflow-y: auto;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
#editor-meta-tabs .tab-panel:has(.editor-card) {
|
|
142
|
+
overflow: hidden;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
.editor-card {
|
|
146
|
+
display: flex;
|
|
147
|
+
flex-direction: column;
|
|
148
|
+
height: 100%;
|
|
149
|
+
}
|
|
150
|
+
|
|
111
151
|
/* Markdown editor */
|
|
112
152
|
.editor-card .card-body {
|
|
113
153
|
display: flex;
|
|
114
154
|
flex-direction: column;
|
|
115
|
-
|
|
155
|
+
flex: 1;
|
|
156
|
+
min-height: 0;
|
|
116
157
|
padding: 0 !important;
|
|
117
158
|
}
|
|
118
159
|
|
|
@@ -1121,3 +1162,39 @@
|
|
|
1121
1162
|
white-space: nowrap;
|
|
1122
1163
|
max-width: 72px;
|
|
1123
1164
|
}
|
|
1165
|
+
|
|
1166
|
+
/* ── Slideover ───────────────────────────────────────────────────────────── */
|
|
1167
|
+
|
|
1168
|
+
.dm-slideover-header {
|
|
1169
|
+
display: flex;
|
|
1170
|
+
align-items: center;
|
|
1171
|
+
justify-content: space-between;
|
|
1172
|
+
padding: .75rem 1rem;
|
|
1173
|
+
border-bottom: 1px solid var(--dm-border, #333);
|
|
1174
|
+
flex-shrink: 0;
|
|
1175
|
+
}
|
|
1176
|
+
|
|
1177
|
+
.dm-slideover-title {
|
|
1178
|
+
margin: 0;
|
|
1179
|
+
font-size: 1rem;
|
|
1180
|
+
font-weight: 600;
|
|
1181
|
+
}
|
|
1182
|
+
|
|
1183
|
+
.dm-slideover-close {
|
|
1184
|
+
padding: .2rem .35rem !important;
|
|
1185
|
+
line-height: 1;
|
|
1186
|
+
font-size: .75rem;
|
|
1187
|
+
}
|
|
1188
|
+
|
|
1189
|
+
.dm-slideover-body {
|
|
1190
|
+
flex: 1;
|
|
1191
|
+
overflow-y: auto;
|
|
1192
|
+
min-height: 0;
|
|
1193
|
+
}
|
|
1194
|
+
|
|
1195
|
+
/* DConfig textarea — monospace for JSON editing */
|
|
1196
|
+
.dconfig-textarea {
|
|
1197
|
+
font-family: monospace;
|
|
1198
|
+
min-height: 100px;
|
|
1199
|
+
resize: vertical;
|
|
1200
|
+
}
|
package/admin/js/api.js
CHANGED
|
@@ -181,6 +181,38 @@ export const api = {
|
|
|
181
181
|
list: () => apiRequest('/plugins', { method: 'GET' }),
|
|
182
182
|
update: (name, data) => apiRequest(`/plugins/${name}`, { method: 'PUT', body: JSON.stringify(data) }),
|
|
183
183
|
adminConfig: () => apiRequest('/plugins/admin-config', { method: 'GET' })
|
|
184
|
+
},
|
|
185
|
+
|
|
186
|
+
// Collections
|
|
187
|
+
collections: {
|
|
188
|
+
list: () => apiRequest('/collections', { method: 'GET' }),
|
|
189
|
+
get: (slug) => apiRequest(`/collections/${slug}`, { method: 'GET' }),
|
|
190
|
+
create: (data) => apiRequest('/collections', { method: 'POST', body: JSON.stringify(data) }),
|
|
191
|
+
update: (slug, data) => apiRequest(`/collections/${slug}`, { method: 'PUT', body: JSON.stringify(data) }),
|
|
192
|
+
delete: (slug) => apiRequest(`/collections/${slug}`, { method: 'DELETE' }),
|
|
193
|
+
|
|
194
|
+
listEntries: (slug, params = {}) => {
|
|
195
|
+
const qs = new URLSearchParams(params).toString();
|
|
196
|
+
return apiRequest(`/collections/${slug}/entries${qs ? '?' + qs : ''}`, { method: 'GET' });
|
|
197
|
+
},
|
|
198
|
+
getEntry: (slug, id) => apiRequest(`/collections/${slug}/entries/${id}`, { method: 'GET' }),
|
|
199
|
+
createEntry: (slug, data) => apiRequest(`/collections/${slug}/entries`, { method: 'POST', body: JSON.stringify({ data }) }),
|
|
200
|
+
updateEntry: (slug, id, data) => apiRequest(`/collections/${slug}/entries/${id}`, { method: 'PUT', body: JSON.stringify({ data }) }),
|
|
201
|
+
deleteEntry: (slug, id) => apiRequest(`/collections/${slug}/entries/${id}`, { method: 'DELETE' }),
|
|
202
|
+
clearEntries: (slug) => apiRequest(`/collections/${slug}/entries`, { method: 'DELETE' }),
|
|
203
|
+
|
|
204
|
+
import: (slug, entries) => apiRequest(`/collections/${slug}/import`, { method: 'POST', body: JSON.stringify({ entries }) }),
|
|
205
|
+
|
|
206
|
+
// Public API (no auth needed when collection is public)
|
|
207
|
+
publicList: (slug, params = {}) => {
|
|
208
|
+
const qs = new URLSearchParams(params).toString();
|
|
209
|
+
return apiRequest(`/collections/${slug}/public${qs ? '?' + qs : ''}`, { method: 'GET' });
|
|
210
|
+
}
|
|
211
|
+
},
|
|
212
|
+
|
|
213
|
+
// Settings (extended)
|
|
214
|
+
settingsExt: {
|
|
215
|
+
testEmail: (to) => apiRequest('/settings/test-email', { method: 'POST', body: JSON.stringify({ to }) })
|
|
184
216
|
}
|
|
185
217
|
};
|
|
186
218
|
|
package/admin/js/app.js
CHANGED
|
@@ -40,17 +40,19 @@ $(() => {
|
|
|
40
40
|
async function fetchSidebarCounts() {
|
|
41
41
|
if (!isAuthenticated()) return {};
|
|
42
42
|
try {
|
|
43
|
-
const [pages, media, users, plugins] = await Promise.all([
|
|
43
|
+
const [pages, media, users, plugins, collections] = await Promise.all([
|
|
44
44
|
api.pages.list().catch(() => []),
|
|
45
45
|
api.media.list().catch(() => []),
|
|
46
46
|
api.users.list().catch(() => []),
|
|
47
|
-
api.plugins.list().catch(() => [])
|
|
47
|
+
api.plugins.list().catch(() => []),
|
|
48
|
+
api.collections.list().catch(() => [])
|
|
48
49
|
]);
|
|
49
50
|
return {
|
|
50
|
-
pages:
|
|
51
|
-
media:
|
|
52
|
-
users:
|
|
53
|
-
plugins:
|
|
51
|
+
pages: pages.length,
|
|
52
|
+
media: media.length,
|
|
53
|
+
users: users.length,
|
|
54
|
+
plugins: plugins.filter(p => p.enabled).length || plugins.length,
|
|
55
|
+
collections: collections.length
|
|
54
56
|
};
|
|
55
57
|
} catch {
|
|
56
58
|
return {};
|
|
@@ -132,6 +134,10 @@ $(() => {
|
|
|
132
134
|
{path: '/plugins', view: 'plugins', title: 'Plugins - Domma CMS'},
|
|
133
135
|
{path: '/documentation', view: 'documentation', title: 'Usage - Domma CMS'},
|
|
134
136
|
{path: '/tutorials', view: 'tutorials', title: 'Tutorials - Domma CMS'},
|
|
137
|
+
{path: '/collections', view: 'collections', title: 'Collections - Domma CMS'},
|
|
138
|
+
{path: '/collections/new', view: 'collectionEditor', title: 'New Collection - Domma CMS'},
|
|
139
|
+
{path: '/collections/edit/:slug', view: 'collectionEditor', title: 'Edit Collection - Domma CMS'},
|
|
140
|
+
{path: '/collections/:slug/entries',view: 'collectionEntries', title: 'Entries - Domma CMS'},
|
|
135
141
|
{ path: '/login', view: 'login', title: 'Sign in - Domma CMS',
|
|
136
142
|
onEnter: () => {
|
|
137
143
|
$('#admin-sidebar').hide();
|
|
@@ -166,7 +172,18 @@ $(() => {
|
|
|
166
172
|
$('#view-container').on('click', '.card-collapsible .card-header', function (e) {
|
|
167
173
|
if ($(e.target).closest('button, a').length) return;
|
|
168
174
|
$(this).closest('.card').toggleClass('card-collapsed');
|
|
169
|
-
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
// Global Ctrl+S / Cmd+S — click the primary save button in any view
|
|
178
|
+
document.addEventListener('keydown', (e) => {
|
|
179
|
+
if (!(e.ctrlKey || e.metaKey) || e.key !== 's') return;
|
|
180
|
+
if (window.location.hash === '#/login') return;
|
|
181
|
+
const saveBtn = document.querySelector('#view-container .view-header button.btn-primary');
|
|
182
|
+
if (saveBtn) {
|
|
183
|
+
e.preventDefault();
|
|
184
|
+
saveBtn.click();
|
|
185
|
+
}
|
|
186
|
+
});
|
|
170
187
|
|
|
171
188
|
// -------------------------------------------------------------------------
|
|
172
189
|
// Dynamic plugin routes (loaded before R.init)
|
|
@@ -38,6 +38,14 @@ export function getSidebarConfig(role, counts = {}, pluginItems = []) {
|
|
|
38
38
|
{id: 'media', text: 'Media', icon: 'image', url: '#/media', section: '#/media', badge: badge(counts.media)}
|
|
39
39
|
);
|
|
40
40
|
|
|
41
|
+
if (canStructure) {
|
|
42
|
+
items.push(
|
|
43
|
+
{divider: true},
|
|
44
|
+
{heading: 'Data'},
|
|
45
|
+
{id: 'collections', text: 'Collections', icon: 'database', url: '#/collections', section: '#/collections', badge: badge(counts.collections)}
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
41
49
|
items.push(
|
|
42
50
|
{divider: true},
|
|
43
51
|
{heading: 'Configuration'}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
<div class="view-header">
|
|
2
|
+
<h1 id="editor-title"><span data-icon="database"></span> <span id="editor-title-text">New Collection</span></h1>
|
|
3
|
+
<div style="display:flex;gap:.5rem;align-items:center;">
|
|
4
|
+
<a href="#/collections" class="btn btn-ghost btn-sm">
|
|
5
|
+
<span data-icon="arrow-left"></span> All Collections
|
|
6
|
+
</a>
|
|
7
|
+
<button id="save-collection-btn" class="btn btn-primary">
|
|
8
|
+
<span data-icon="save"></span> Save
|
|
9
|
+
</button>
|
|
10
|
+
</div>
|
|
11
|
+
</div>
|
|
12
|
+
|
|
13
|
+
<div class="tabs" id="collection-tabs">
|
|
14
|
+
<div class="tab-list">
|
|
15
|
+
<button class="tab-item active">Settings</button>
|
|
16
|
+
<button class="tab-item">Fields</button>
|
|
17
|
+
<button class="tab-item">API Access</button>
|
|
18
|
+
</div>
|
|
19
|
+
<div class="tab-content">
|
|
20
|
+
|
|
21
|
+
<!-- Settings tab -->
|
|
22
|
+
<div class="tab-panel active">
|
|
23
|
+
<div class="card mb-4">
|
|
24
|
+
<div class="card-body">
|
|
25
|
+
<div class="row mb-3">
|
|
26
|
+
<div class="col-7">
|
|
27
|
+
<label class="form-label">Title</label>
|
|
28
|
+
<input id="field-title" type="text" class="form-input" placeholder="e.g. Products">
|
|
29
|
+
</div>
|
|
30
|
+
<div class="col-5">
|
|
31
|
+
<label class="form-label">Slug</label>
|
|
32
|
+
<input id="field-slug" type="text" class="form-input" placeholder="products">
|
|
33
|
+
<p class="form-hint text-muted" style="margin-top:.3rem;font-size:.8rem;" id="slug-hint">Auto-generated from title. Cannot be changed after creation.</p>
|
|
34
|
+
</div>
|
|
35
|
+
</div>
|
|
36
|
+
<div class="row">
|
|
37
|
+
<div class="col">
|
|
38
|
+
<label class="form-label">Description</label>
|
|
39
|
+
<textarea id="field-description" class="form-input" rows="2" placeholder="Optional description…"></textarea>
|
|
40
|
+
</div>
|
|
41
|
+
</div>
|
|
42
|
+
</div>
|
|
43
|
+
</div>
|
|
44
|
+
</div>
|
|
45
|
+
|
|
46
|
+
<!-- Fields tab -->
|
|
47
|
+
<div class="tab-panel">
|
|
48
|
+
<div class="card mb-4">
|
|
49
|
+
<div class="card-header" style="display:flex;justify-content:space-between;align-items:center;">
|
|
50
|
+
<h2>Fields</h2>
|
|
51
|
+
<button id="add-field-btn" class="btn btn-ghost btn-sm" type="button">
|
|
52
|
+
<span data-icon="plus"></span> Add Field
|
|
53
|
+
</button>
|
|
54
|
+
</div>
|
|
55
|
+
<div class="card-body" style="padding:0;">
|
|
56
|
+
<div id="fields-list" style="padding:1rem;">
|
|
57
|
+
<p class="text-muted" id="fields-empty-msg" style="text-align:center;padding:2rem 0;">No fields yet. Click "Add Field" to get started.</p>
|
|
58
|
+
</div>
|
|
59
|
+
</div>
|
|
60
|
+
</div>
|
|
61
|
+
</div>
|
|
62
|
+
|
|
63
|
+
<!-- API Access tab -->
|
|
64
|
+
<div class="tab-panel">
|
|
65
|
+
<div class="card mb-4">
|
|
66
|
+
<div class="card-header">
|
|
67
|
+
<h2>Public API Access</h2>
|
|
68
|
+
</div>
|
|
69
|
+
<div class="card-body">
|
|
70
|
+
<p class="text-muted mb-4" style="font-size:.875rem;">Control which operations are available via the public API (<code>/api/collections/{slug}/public</code>). Admin endpoints are always protected by role.</p>
|
|
71
|
+
|
|
72
|
+
<div id="api-access-rows">
|
|
73
|
+
<!-- Rows built by JS -->
|
|
74
|
+
</div>
|
|
75
|
+
</div>
|
|
76
|
+
</div>
|
|
77
|
+
</div>
|
|
78
|
+
|
|
79
|
+
</div>
|
|
80
|
+
</div>
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
<div class="view-header">
|
|
2
|
+
<h1><span data-icon="database"></span> <span id="entries-title">Entries</span></h1>
|
|
3
|
+
<div style="display:flex;gap:.5rem;align-items:center;">
|
|
4
|
+
<a href="#/collections" class="btn btn-ghost btn-sm">
|
|
5
|
+
<span data-icon="arrow-left"></span> Collections
|
|
6
|
+
</a>
|
|
7
|
+
<a id="edit-schema-btn" href="#/collections" class="btn btn-ghost btn-sm">
|
|
8
|
+
<span data-icon="edit-3"></span> Edit Schema
|
|
9
|
+
</a>
|
|
10
|
+
<button id="import-btn" class="btn btn-ghost btn-sm">
|
|
11
|
+
<span data-icon="upload"></span> Import
|
|
12
|
+
</button>
|
|
13
|
+
<button id="export-btn" class="btn btn-ghost btn-sm">
|
|
14
|
+
<span data-icon="download"></span> Export
|
|
15
|
+
</button>
|
|
16
|
+
<button id="clear-all-btn" class="btn btn-danger btn-sm">
|
|
17
|
+
<span data-icon="trash-2"></span> Clear All
|
|
18
|
+
</button>
|
|
19
|
+
<button id="add-entry-btn" class="btn btn-primary">
|
|
20
|
+
<span data-icon="plus"></span> Add Entry
|
|
21
|
+
</button>
|
|
22
|
+
</div>
|
|
23
|
+
</div>
|
|
24
|
+
|
|
25
|
+
<div class="card mb-3">
|
|
26
|
+
<div class="card-body" style="display:flex;gap:.75rem;align-items:center;flex-wrap:wrap;">
|
|
27
|
+
<input id="entry-search" type="text" class="form-input" placeholder="Search entries…" style="flex:1;min-width:200px;">
|
|
28
|
+
<span id="entry-count" class="text-muted" style="white-space:nowrap;font-size:.85rem;"></span>
|
|
29
|
+
</div>
|
|
30
|
+
</div>
|
|
31
|
+
|
|
32
|
+
<div class="card">
|
|
33
|
+
<div class="card-body">
|
|
34
|
+
<div id="entries-table"></div>
|
|
35
|
+
</div>
|
|
36
|
+
</div>
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<div class="view-header">
|
|
2
|
+
<h1><span data-icon="database"></span> Collections</h1>
|
|
3
|
+
<button id="create-collection-btn" class="btn btn-primary">
|
|
4
|
+
<span data-icon="plus"></span> New Collection
|
|
5
|
+
</button>
|
|
6
|
+
</div>
|
|
7
|
+
|
|
8
|
+
<div class="card">
|
|
9
|
+
<div class="card-body">
|
|
10
|
+
<div id="collections-table"></div>
|
|
11
|
+
</div>
|
|
12
|
+
</div>
|
|
@@ -113,6 +113,142 @@ Your Markdown content goes here.</code></pre>
|
|
|
113
113
|
</div>
|
|
114
114
|
</div>
|
|
115
115
|
|
|
116
|
+
<!-- Domma Config -->
|
|
117
|
+
<div class="card card-collapsible mb-4">
|
|
118
|
+
<div class="card-header" role="button" tabindex="0">
|
|
119
|
+
<div class="card-header-content"><h2><span data-icon="zap"></span> Domma Config (DConfig)</h2></div>
|
|
120
|
+
<span class="card-collapse-icon" data-icon="chevron-down"></span>
|
|
121
|
+
</div>
|
|
122
|
+
<div class="card-body docs-body">
|
|
123
|
+
<p>DConfig lets you wire up declarative behaviour on any public page — no JavaScript required. Define event
|
|
124
|
+
handlers and class toggles in JSON; the page runtime applies them automatically on load.</p>
|
|
125
|
+
|
|
126
|
+
<p>DConfig can be set in two ways, and both are merged at runtime (inline shortcode wins on selector
|
|
127
|
+
conflict):</p>
|
|
128
|
+
<ul>
|
|
129
|
+
<li><strong>Page editor</strong> — the <em>DConfig</em> collapsible section accepts a JSON blob stored in page
|
|
130
|
+
frontmatter and injected as <code>window.__CMS_DCONFIG__</code>.
|
|
131
|
+
</li>
|
|
132
|
+
<li><strong>Inline shortcode</strong> — place one or more <code>[dconfig]…[/dconfig]</code> blocks directly in
|
|
133
|
+
the page body Markdown.
|
|
134
|
+
</li>
|
|
135
|
+
</ul>
|
|
136
|
+
|
|
137
|
+
<h3>Schema</h3>
|
|
138
|
+
<pre class="code-block"><code>{
|
|
139
|
+
"#selector": {
|
|
140
|
+
"events": {
|
|
141
|
+
"eventName": {
|
|
142
|
+
"target": "#target-selector",
|
|
143
|
+
"action": "value"
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}</code></pre>
|
|
148
|
+
|
|
149
|
+
<p>Multiple selectors and multiple event types can appear in a single config block.</p>
|
|
150
|
+
|
|
151
|
+
<h3>Supported events</h3>
|
|
152
|
+
<table class="table table-sm">
|
|
153
|
+
<thead>
|
|
154
|
+
<tr>
|
|
155
|
+
<th>Event</th>
|
|
156
|
+
<th>Description</th>
|
|
157
|
+
</tr>
|
|
158
|
+
</thead>
|
|
159
|
+
<tbody>
|
|
160
|
+
<tr>
|
|
161
|
+
<td><code>click</code></td>
|
|
162
|
+
<td>Fires when the selector element is clicked.</td>
|
|
163
|
+
</tr>
|
|
164
|
+
</tbody>
|
|
165
|
+
</table>
|
|
166
|
+
|
|
167
|
+
<h3>Supported actions</h3>
|
|
168
|
+
<table class="table table-sm">
|
|
169
|
+
<thead>
|
|
170
|
+
<tr>
|
|
171
|
+
<th>Action key</th>
|
|
172
|
+
<th>Value type</th>
|
|
173
|
+
<th>Description</th>
|
|
174
|
+
</tr>
|
|
175
|
+
</thead>
|
|
176
|
+
<tbody>
|
|
177
|
+
<tr>
|
|
178
|
+
<td><code>toggleClass</code></td>
|
|
179
|
+
<td><code>string</code></td>
|
|
180
|
+
<td>Adds the class if absent, removes it if present, on the <code>target</code> element. Typical use:
|
|
181
|
+
show/hide content with a <code>hidden</code> class.
|
|
182
|
+
</td>
|
|
183
|
+
</tr>
|
|
184
|
+
</tbody>
|
|
185
|
+
</table>
|
|
186
|
+
|
|
187
|
+
<p><em>Additional actions (addClass, removeClass, setAttribute, scroll-to) are planned for future releases.</em>
|
|
188
|
+
</p>
|
|
189
|
+
|
|
190
|
+
<h3>Targeting shortcode elements with <code>id</code></h3>
|
|
191
|
+
<p>All shortcodes support an <code>id</code> attribute, which is written directly onto the generated element.
|
|
192
|
+
Use this instead of raw HTML when you want DConfig to target a card, column, or grid:</p>
|
|
193
|
+
<pre class="code-block"><code>[card id="my-panel" title="Hidden Details"]
|
|
194
|
+
Content goes here.
|
|
195
|
+
[/card]
|
|
196
|
+
|
|
197
|
+
[dconfig]
|
|
198
|
+
{
|
|
199
|
+
"#my-btn": {
|
|
200
|
+
"events": {
|
|
201
|
+
"click": { "target": "#my-panel", "toggleClass": "hidden" }
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
[/dconfig]</code></pre>
|
|
206
|
+
<p>Supported on: <code>[card]</code>, <code>[col]</code>, <code>[row]</code>, <code>[grid]</code>.</p>
|
|
207
|
+
|
|
208
|
+
<h3>Using the <code>[dconfig]</code> shortcode</h3>
|
|
209
|
+
<pre class="code-block"><code>[dconfig]
|
|
210
|
+
{
|
|
211
|
+
"#my-btn": {
|
|
212
|
+
"events": {
|
|
213
|
+
"click": { "target": "#my-panel", "toggleClass": "hidden" }
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
[/dconfig]
|
|
218
|
+
|
|
219
|
+
<button id="my-btn" class="btn btn-primary">Toggle</button>
|
|
220
|
+
|
|
221
|
+
<div id="my-panel" class="card hidden">
|
|
222
|
+
<div class="card-body">Hidden content.</div>
|
|
223
|
+
</div></code></pre>
|
|
224
|
+
|
|
225
|
+
<h3>Multiple selectors</h3>
|
|
226
|
+
<p>Any number of selectors can appear in one block. Selectors are matched with
|
|
227
|
+
<code>document.querySelector</code> — use IDs, classes, or attribute selectors.</p>
|
|
228
|
+
<pre class="code-block"><code>[dconfig]
|
|
229
|
+
{
|
|
230
|
+
"#show-btn": {
|
|
231
|
+
"events": {
|
|
232
|
+
"click": { "target": "#panel", "toggleClass": "hidden" }
|
|
233
|
+
}
|
|
234
|
+
},
|
|
235
|
+
"#hide-btn": {
|
|
236
|
+
"events": {
|
|
237
|
+
"click": { "target": "#panel", "toggleClass": "hidden" }
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
[/dconfig]</code></pre>
|
|
242
|
+
|
|
243
|
+
<h3>Using the page editor DConfig section</h3>
|
|
244
|
+
<p>The same JSON (without the shortcode tags) can be pasted directly into the <em>DConfig</em> card in the page
|
|
245
|
+
editor. This is useful when the config should apply to the whole page without appearing in the body content.
|
|
246
|
+
Inline <code>[dconfig]</code> shortcodes are applied last and will win on any shared selector key.</p>
|
|
247
|
+
|
|
248
|
+
<p>See live demos on the <a href="/resources/interactive" target="_blank">Interactive Resources page</a>.</p>
|
|
249
|
+
</div>
|
|
250
|
+
</div>
|
|
251
|
+
|
|
116
252
|
<!-- Settings -->
|
|
117
253
|
<div class="card card-collapsible mb-4">
|
|
118
254
|
<div class="card-header" role="button" tabindex="0">
|
|
@@ -9,16 +9,20 @@
|
|
|
9
9
|
<span class="card-collapse-icon" data-icon="chevron-down"></span>
|
|
10
10
|
</div>
|
|
11
11
|
<div class="card-body">
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
<div class="row mb-3">
|
|
13
|
+
<div class="col-4">
|
|
14
14
|
<label class="form-label">Site Name</label>
|
|
15
15
|
<input id="field-brand-text" type="text" class="form-input" placeholder="My Site">
|
|
16
16
|
</div>
|
|
17
|
-
|
|
17
|
+
<div class="col-3">
|
|
18
18
|
<label class="form-label">Brand URL</label>
|
|
19
19
|
<input id="field-brand-url" type="text" class="form-input" value="/">
|
|
20
20
|
</div>
|
|
21
21
|
<div class="col-3">
|
|
22
|
+
<label class="form-label">Icon <span class="text-muted" style="font-weight:normal;font-size:.8em;">(optional)</span></label>
|
|
23
|
+
<input id="field-brand-icon" type="text" class="form-input" placeholder="e.g. star, home">
|
|
24
|
+
</div>
|
|
25
|
+
<div class="col-2">
|
|
22
26
|
<label class="form-label">Variant</label>
|
|
23
27
|
<select id="field-nav-variant" class="form-select">
|
|
24
28
|
<option value="dark">Dark</option>
|
|
@@ -27,10 +31,12 @@
|
|
|
27
31
|
</select>
|
|
28
32
|
</div>
|
|
29
33
|
</div>
|
|
34
|
+
<p class="form-hint">Icon uses Domma icon names (e.g. <code>star</code>, <code>home</code>, <code>bolt</code>).
|
|
35
|
+
Leave blank for text-only brand.</p>
|
|
30
36
|
</div>
|
|
31
37
|
</div>
|
|
32
38
|
|
|
33
|
-
<div class="card card-collapsible">
|
|
39
|
+
<div class="card card-collapsible mb-4">
|
|
34
40
|
<div class="card-header" role="button" tabindex="0">
|
|
35
41
|
<div class="card-header-content"><h2>Menu Items</h2></div>
|
|
36
42
|
<button id="add-nav-item" class="btn btn-sm btn-outline"><span data-icon="plus"></span> Add Item</button>
|
|
@@ -48,3 +54,19 @@
|
|
|
48
54
|
<div id="nav-items-list"></div>
|
|
49
55
|
</div>
|
|
50
56
|
</div>
|
|
57
|
+
|
|
58
|
+
<div class="card card-collapsible">
|
|
59
|
+
<div class="card-header" role="button" tabindex="0">
|
|
60
|
+
<div class="card-header-content"><h2>Footer Links</h2></div>
|
|
61
|
+
<button id="add-footer-link" class="btn btn-sm btn-outline"><span data-icon="plus"></span> Add Link</button>
|
|
62
|
+
<span class="card-collapse-icon" data-icon="chevron-down"></span>
|
|
63
|
+
</div>
|
|
64
|
+
<div class="card-body p-0">
|
|
65
|
+
<div class="nav-items-header">
|
|
66
|
+
<span class="nav-col-main text-muted"><small>Label</small></span>
|
|
67
|
+
<span class="nav-col-main text-muted"><small>URL</small></span>
|
|
68
|
+
<span class="nav-col-action"></span>
|
|
69
|
+
</div>
|
|
70
|
+
<div id="footer-links-list"></div>
|
|
71
|
+
</div>
|
|
72
|
+
</div>
|