domma-cms 0.43.1 → 0.45.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +53 -53
- package/README.md +27 -27
- package/admin/css/admin.css +1 -1
- package/admin/dist/domma/domma-tools.css +3 -3
- package/admin/dist/domma/domma-tools.min.js +3 -3
- package/admin/index.html +2 -2
- package/admin/js/app.js +1 -1
- package/admin/js/lib/card-builder.js +2 -2
- package/admin/js/lib/crud-tutorial.js +1 -1
- package/admin/js/lib/effect-defs.js +1 -1
- package/admin/js/lib/effects-builder.js +1 -1
- package/admin/js/lib/image-editor.js +1 -1
- package/admin/js/lib/markdown-toolbar.js +2 -2
- package/admin/js/lib/project-context.js +1 -1
- package/admin/js/lib/project-quick-create.js +1 -1
- package/admin/js/lib/scribe-composer.js +1 -1
- package/admin/js/lib/server-restart.js +1 -1
- package/admin/js/lib/shortcode-context-menu.js +2 -2
- package/admin/js/lib/simple-editor.js +1 -1
- package/admin/js/lib/themes.js +1 -1
- package/admin/js/lib/timeline-builder.js +1 -1
- package/admin/js/templates/action-editor.html +8 -8
- package/admin/js/templates/api-endpoint-editor.html +7 -7
- package/admin/js/templates/api-endpoints.html +1 -1
- package/admin/js/templates/api-reference.html +1 -1
- package/admin/js/templates/block-editor.html +6 -6
- package/admin/js/templates/collection-editor.html +1 -1
- package/admin/js/templates/component-editor.html +2 -2
- package/admin/js/templates/dashboard/cache.html +3 -3
- package/admin/js/templates/dashboard/journeys.html +3 -3
- package/admin/js/templates/dashboard/kpi-strip.html +3 -3
- package/admin/js/templates/dashboard/top-pages.html +1 -1
- package/admin/js/templates/dashboard/traffic-chart.html +1 -1
- package/admin/js/templates/docs/api-actions.html +1 -1
- package/admin/js/templates/docs/api-collections.html +4 -4
- package/admin/js/templates/docs/api-pages.html +2 -2
- package/admin/js/templates/docs/api-plugins.html +1 -1
- package/admin/js/templates/docs/api-settings.html +2 -2
- package/admin/js/templates/docs/api-users.html +1 -1
- package/admin/js/templates/docs/components-howto.html +4 -4
- package/admin/js/templates/docs/components-reference.html +21 -21
- package/admin/js/templates/docs/components-rules.html +14 -14
- package/admin/js/templates/docs/components-walkthrough.html +13 -13
- package/admin/js/templates/docs/tutorial-crud.html +49 -49
- package/admin/js/templates/docs/tutorial-forms.html +4 -4
- package/admin/js/templates/docs/tutorial-plugin.html +10 -10
- package/admin/js/templates/docs/usage-actions.html +6 -6
- package/admin/js/templates/docs/usage-cta-shortcode.html +9 -9
- package/admin/js/templates/docs/usage-dconfig.html +4 -4
- package/admin/js/templates/docs/usage-navigation.html +1 -1
- package/admin/js/templates/docs/usage-pages.html +1 -1
- package/admin/js/templates/docs/usage-shortcodes.html +24 -24
- package/admin/js/templates/docs/usage-users-roles.html +3 -3
- package/admin/js/templates/docs/usage-views.html +4 -4
- package/admin/js/templates/documentation.html +1 -1
- package/admin/js/templates/effects.html +25 -25
- package/admin/js/templates/form-editor.html +5 -5
- package/admin/js/templates/login.html +5 -5
- package/admin/js/templates/menu-editor.html +10 -10
- package/admin/js/templates/navigation.html +11 -11
- package/admin/js/templates/notifications.html +1 -1
- package/admin/js/templates/page-editor.html +4 -4
- package/admin/js/templates/pro-docs.html +14 -14
- package/admin/js/templates/role-editor.html +1 -1
- package/admin/js/templates/settings.html +10 -10
- package/admin/js/templates/tutorials.html +3 -3
- package/admin/js/templates/view-editor.html +15 -15
- package/admin/js/views/action-editor.js +1 -1
- package/admin/js/views/actions-list.js +1 -1
- package/admin/js/views/api-endpoint-editor.js +2 -2
- package/admin/js/views/api-endpoints.js +2 -2
- package/admin/js/views/api-tokens.js +3 -3
- package/admin/js/views/blocks.js +2 -2
- package/admin/js/views/collection-editor.js +1 -1
- package/admin/js/views/collection-entries.js +1 -1
- package/admin/js/views/collections.js +1 -1
- package/admin/js/views/component-editor.js +1 -1
- package/admin/js/views/components.js +4 -4
- package/admin/js/views/dashboard/widgets/cache.js +1 -1
- package/admin/js/views/dashboard/widgets/journeys.js +1 -1
- package/admin/js/views/dashboard/widgets/kpi-strip.js +1 -1
- package/admin/js/views/form-editor.js +7 -7
- package/admin/js/views/form-submissions.js +1 -1
- package/admin/js/views/forms.js +1 -1
- package/admin/js/views/layouts.js +1 -1
- package/admin/js/views/login.js +2 -2
- package/admin/js/views/menu-editor.js +3 -3
- package/admin/js/views/menu-locations.js +1 -1
- package/admin/js/views/menus.js +3 -3
- package/admin/js/views/navigation.js +8 -8
- package/admin/js/views/page-editor.js +40 -40
- package/admin/js/views/pages.js +3 -3
- package/admin/js/views/plugin-code.js +2 -2
- package/admin/js/views/plugins.js +1 -1
- package/admin/js/views/project-settings.js +1 -1
- package/admin/js/views/projects.js +1 -1
- package/admin/js/views/roles.js +1 -1
- package/admin/js/views/user-editor.js +1 -1
- package/admin/js/views/users.js +3 -3
- package/admin/js/views/view-editor.js +1 -1
- package/admin/js/views/view-preview.js +1 -1
- package/admin/js/views/views-list.js +1 -1
- package/bin/cli.js +8 -8
- package/bin/lib/config-merge.js +5 -5
- package/bin/update.js +16 -16
- package/config/site.json +86 -86
- package/package.json +2 -2
- package/plugins/_template/admin/views/index.js +1 -1
- package/plugins/_template/config.js +1 -1
- package/plugins/_template/plugin.js +1 -1
- package/plugins/analytics/admin/templates/analytics.html +3 -3
- package/plugins/analytics/admin/views/analytics.js +1 -1
- package/plugins/analytics/config.js +1 -1
- package/plugins/analytics/plugin.js +4 -4
- package/plugins/analytics/plugin.json +1 -1
- package/plugins/analytics/public/inject-body.html +2 -2
- package/plugins/analytics/public/inject-head.html +1 -1
- package/plugins/blog/admin/templates/blog.html +4 -4
- package/plugins/blog/admin/views/blog.js +3 -3
- package/plugins/blog/admin/views/categories.js +2 -2
- package/plugins/blog/admin/views/comments.js +4 -4
- package/plugins/blog/admin/views/post-editor.js +1 -1
- package/plugins/blog/plugin.js +2 -2
- package/plugins/blog/plugin.json +4 -4
- package/plugins/contacts/admin/views/contacts.js +8 -8
- package/plugins/contacts/plugin.js +4 -4
- package/plugins/contacts/plugin.json +1 -1
- package/plugins/invoice/admin/templates/editor.html +6 -6
- package/plugins/invoice/admin/templates/index.html +4 -4
- package/plugins/invoice/admin/views/editor.js +3 -3
- package/plugins/invoice/admin/views/index.js +4 -4
- package/plugins/invoice/admin/views/issuers.js +1 -1
- package/plugins/invoice/admin/views/party-view.js +3 -3
- package/plugins/invoice/admin/views/receivers.js +1 -1
- package/plugins/invoice/config.js +1 -1
- package/plugins/invoice/plugin.js +1 -1
- package/plugins/invoice/plugin.json +4 -4
- package/plugins/notes/admin/views/notes.js +4 -4
- package/plugins/notes/plugin.js +1 -1
- package/plugins/notes/plugin.json +1 -1
- package/plugins/site-search/admin/templates/site-search.html +3 -3
- package/plugins/site-search/admin/views/site-search.js +1 -1
- package/plugins/site-search/config.js +1 -1
- package/plugins/site-search/plugin.js +4 -4
- package/plugins/site-search/plugin.json +1 -1
- package/plugins/surveys/admin/templates/results.html +1 -1
- package/plugins/surveys/admin/templates/survey-editor.html +4 -4
- package/plugins/surveys/admin/views/audience.js +6 -6
- package/plugins/surveys/admin/views/results.js +3 -3
- package/plugins/surveys/admin/views/survey-editor.js +1 -1
- package/plugins/surveys/admin/views/surveys.js +2 -2
- package/plugins/surveys/plugin.js +1 -1
- package/plugins/surveys/plugin.json +4 -4
- package/plugins/theme-switcher/admin/views/theme-switcher.js +1 -1
- package/plugins/theme-switcher/plugin.json +3 -3
- package/plugins/theme-switcher/public/inject-body.html +28 -28
- package/plugins/theme-switcher/public/inject-head.html +1 -1
- package/plugins/todo/plugin.js +1 -1
- package/public/css/forms.css +1 -1
- package/public/js/collection-browser.js +1 -1
- package/public/js/collection-context.js +1 -1
- package/public/js/form-logic-engine.js +1 -1
- package/public/js/forms.js +2 -1
- package/public/js/site.js +1 -1
- package/scripts/build.js +100 -6
- package/scripts/create-plugin.js +2 -2
- package/scripts/fresh.js +2 -2
- package/scripts/gen-instance-secret.js +1 -1
- package/scripts/pro.js +12 -12
- package/scripts/reset.js +2 -2
- package/scripts/setup.js +15 -15
- package/scripts/users.js +5 -5
- package/scripts/verify-assets.mjs +6 -6
- package/server/config.js +1 -1
- package/server/middleware/auth.js +11 -11
- package/server/middleware/managerAuth.js +3 -3
- package/server/routes/api/actions.js +4 -4
- package/server/routes/api/api-endpoints.js +7 -7
- package/server/routes/api/api-tokens.js +5 -5
- package/server/routes/api/auth.js +10 -10
- package/server/routes/api/blocks.js +3 -3
- package/server/routes/api/collections.js +18 -18
- package/server/routes/api/dashboard.js +5 -5
- package/server/routes/api/effects.js +2 -2
- package/server/routes/api/endpoints-public.js +5 -5
- package/server/routes/api/forms.js +47 -47
- package/server/routes/api/menu-locations.js +3 -3
- package/server/routes/api/menus.js +7 -7
- package/server/routes/api/notifications.js +2 -2
- package/server/routes/api/pages.js +14 -2
- package/server/routes/api/plugin-marketplace.js +3 -3
- package/server/routes/api/plugins.js +6 -6
- package/server/routes/api/projects.js +9 -9
- package/server/routes/api/scaffold.js +7 -7
- package/server/routes/api/settings.js +6 -6
- package/server/routes/api/sidebar.js +1 -1
- package/server/routes/api/users.js +5 -5
- package/server/routes/api/versions.js +1 -1
- package/server/routes/api/views.js +1 -1
- package/server/routes/docs-public.js +3 -3
- package/server/routes/public.js +14 -14
- package/server/server.js +34 -16
- package/server/services/actions.js +19 -19
- package/server/services/adapters/FileAdapter.js +2 -2
- package/server/services/adapters/MongoAdapter.js +5 -5
- package/server/services/apiEndpoints.js +15 -15
- package/server/services/apiTokens.js +7 -7
- package/server/services/blocks.js +13 -13
- package/server/services/cache/drivers/MemoryDriver.js +3 -3
- package/server/services/cache/drivers/NoneDriver.js +1 -1
- package/server/services/cache/index.js +5 -5
- package/server/services/collections.js +14 -14
- package/server/services/components.js +9 -9
- package/server/services/connectionManager.js +4 -4
- package/server/services/content.js +25 -2
- package/server/services/docs.js +14 -14
- package/server/services/email.js +1 -1
- package/server/services/filterEngine.js +6 -6
- package/server/services/forms.js +18 -10
- package/server/services/health.js +1 -1
- package/server/services/hooks.js +7 -7
- package/server/services/images.js +4 -4
- package/server/services/managerClient.js +1 -1
- package/server/services/markdown.js +79 -79
- package/server/services/menuRender.js +5 -5
- package/server/services/menus-migration.js +3 -3
- package/server/services/menus.js +31 -31
- package/server/services/permissionRegistry.js +1 -1
- package/server/services/pluginFiles.js +5 -5
- package/server/services/pluginInstaller.js +17 -17
- package/server/services/pluginScaffold.js +1 -1
- package/server/services/plugins.js +17 -17
- package/server/services/presetCollections.js +2 -2
- package/server/services/projects.js +35 -26
- package/server/services/recipes/contact-list.json +1 -1
- package/server/services/recipes/onboarding.json +8 -8
- package/server/services/references.js +8 -8
- package/server/services/renderer.js +9 -9
- package/server/services/roles.js +9 -9
- package/server/services/rowAccess.js +10 -10
- package/server/services/scaffolder.js +30 -30
- package/server/services/sidebar-migration.js +10 -10
- package/server/services/sitemap.js +1 -1
- package/server/services/userProfiles.js +4 -4
- package/server/services/userRoles.js +7 -7
- package/server/services/users.js +5 -5
- package/server/services/versions.js +3 -3
- package/server/services/viewPipeline.js +6 -6
- package/server/services/views.js +11 -11
- package/server/templates/page.html +5 -5
- package/config/navigation.json.bak +0 -31
|
@@ -138,7 +138,7 @@ export const contactsView = {
|
|
|
138
138
|
return;
|
|
139
139
|
}
|
|
140
140
|
|
|
141
|
-
// Render functions return HTML strings
|
|
141
|
+
// Render functions return HTML strings - values are escaped via esc()
|
|
142
142
|
// T.create passes these through Domma's DOMPurify sanitiser
|
|
143
143
|
contactsTable = T.create('#contacts-table', {
|
|
144
144
|
data: allContacts,
|
|
@@ -154,20 +154,20 @@ export const contactsView = {
|
|
|
154
154
|
key: 'email',
|
|
155
155
|
title: 'Email',
|
|
156
156
|
sortable: true,
|
|
157
|
-
render: (v) => v ? `<a href="mailto:${esc(v)}">${esc(v)}</a>` : '
|
|
157
|
+
render: (v) => v ? `<a href="mailto:${esc(v)}">${esc(v)}</a>` : '-'
|
|
158
158
|
},
|
|
159
159
|
{
|
|
160
160
|
key: 'phone',
|
|
161
161
|
title: 'Phone',
|
|
162
162
|
sortable: false,
|
|
163
|
-
render: (v) => esc(v) || '
|
|
163
|
+
render: (v) => esc(v) || '-'
|
|
164
164
|
},
|
|
165
165
|
{
|
|
166
166
|
key: 'groups',
|
|
167
167
|
title: 'Groups',
|
|
168
168
|
sortable: false,
|
|
169
169
|
render: (v) => {
|
|
170
|
-
if (!Array.isArray(v) || v.length === 0) return '
|
|
170
|
+
if (!Array.isArray(v) || v.length === 0) return '-';
|
|
171
171
|
return v.map((g) => `<span class="badge badge-primary" style="margin-right:2px;">${esc(g)}</span>`).join('');
|
|
172
172
|
}
|
|
173
173
|
},
|
|
@@ -204,7 +204,7 @@ export const contactsView = {
|
|
|
204
204
|
});
|
|
205
205
|
}
|
|
206
206
|
|
|
207
|
-
// Event delegation on table container
|
|
207
|
+
// Event delegation on table container - avoids namespaced delegation issues
|
|
208
208
|
const tableContainer = $container.find('#contacts-table').get(0);
|
|
209
209
|
if (tableContainer) {
|
|
210
210
|
tableContainer.addEventListener('click', async (e) => {
|
|
@@ -317,7 +317,7 @@ export const contactsView = {
|
|
|
317
317
|
const span = document.createElement('span');
|
|
318
318
|
span.className = 'badge badge-primary group-tag-badge';
|
|
319
319
|
span.style.marginRight = '2px';
|
|
320
|
-
span.textContent = g; // textContent
|
|
320
|
+
span.textContent = g; // textContent - safe, no XSS
|
|
321
321
|
display.appendChild(span);
|
|
322
322
|
});
|
|
323
323
|
}
|
|
@@ -385,7 +385,7 @@ export const contactsView = {
|
|
|
385
385
|
|
|
386
386
|
const label = document.createElement('span');
|
|
387
387
|
label.className = 'badge badge-primary';
|
|
388
|
-
label.textContent = group; // textContent
|
|
388
|
+
label.textContent = group; // textContent - safe
|
|
389
389
|
row.appendChild(label);
|
|
390
390
|
|
|
391
391
|
const actions = document.createElement('div');
|
|
@@ -454,7 +454,7 @@ export const contactsView = {
|
|
|
454
454
|
item.style.background = 'var(--dm-primary-light, rgba(99,102,241,0.1))';
|
|
455
455
|
item.style.fontWeight = '600';
|
|
456
456
|
}
|
|
457
|
-
item.textContent = g; // textContent
|
|
457
|
+
item.textContent = g; // textContent - safe
|
|
458
458
|
item.addEventListener('click', () => {
|
|
459
459
|
const idx = selectedGroups.indexOf(g);
|
|
460
460
|
if (idx === -1) {
|
|
@@ -57,7 +57,7 @@ export default async function contactsPlugin(fastify, options) {
|
|
|
57
57
|
// Contacts CRUD
|
|
58
58
|
// -------------------------------------------------------------------------
|
|
59
59
|
|
|
60
|
-
/** GET /api/plugins/contacts/contacts
|
|
60
|
+
/** GET /api/plugins/contacts/contacts - supports ?q=, ?group=, ?favourites=true */
|
|
61
61
|
fastify.get('/contacts', {preHandler: [authenticate]}, async (request, reply) => {
|
|
62
62
|
const uid = userId(request);
|
|
63
63
|
const { q, group, favourites } = request.query;
|
|
@@ -124,7 +124,7 @@ export default async function contactsPlugin(fastify, options) {
|
|
|
124
124
|
return reply.send(toContact(updated));
|
|
125
125
|
});
|
|
126
126
|
|
|
127
|
-
/** DELETE /api/plugins/contacts/contacts/bulk
|
|
127
|
+
/** DELETE /api/plugins/contacts/contacts/bulk - must be before /:id */
|
|
128
128
|
fastify.delete('/contacts/bulk', {preHandler: [authenticate]}, async (request, reply) => {
|
|
129
129
|
const uid = userId(request);
|
|
130
130
|
const { ids } = request.body ?? {};
|
|
@@ -187,7 +187,7 @@ export default async function contactsPlugin(fastify, options) {
|
|
|
187
187
|
return reply.code(201).send({ name: trimmed });
|
|
188
188
|
});
|
|
189
189
|
|
|
190
|
-
/** PUT /api/plugins/contacts/groups/:name
|
|
190
|
+
/** PUT /api/plugins/contacts/groups/:name - rename + cascade */
|
|
191
191
|
fastify.put('/groups/:name', {preHandler: [authenticate]}, async (request, reply) => {
|
|
192
192
|
const oldName = decodeURIComponent(request.params.name);
|
|
193
193
|
const { newName } = request.body ?? {};
|
|
@@ -223,7 +223,7 @@ export default async function contactsPlugin(fastify, options) {
|
|
|
223
223
|
return reply.send({ name: trimmed });
|
|
224
224
|
});
|
|
225
225
|
|
|
226
|
-
/** DELETE /api/plugins/contacts/groups/:name
|
|
226
|
+
/** DELETE /api/plugins/contacts/groups/:name - delete + cascade */
|
|
227
227
|
fastify.delete('/groups/:name', {preHandler: [authenticate]}, async (request, reply) => {
|
|
228
228
|
const name = decodeURIComponent(request.params.name);
|
|
229
229
|
|
|
@@ -90,16 +90,16 @@
|
|
|
90
90
|
<div class="card-body">
|
|
91
91
|
<div class="d-flex justify-content-between mb-2">
|
|
92
92
|
<span class="text-muted">Subtotal</span>
|
|
93
|
-
<span id="t-subtotal" style="font-variant-numeric:tabular-nums;"
|
|
93
|
+
<span id="t-subtotal" style="font-variant-numeric:tabular-nums;">-</span>
|
|
94
94
|
</div>
|
|
95
95
|
<div class="d-flex justify-content-between mb-2" id="vat-row">
|
|
96
96
|
<span class="text-muted">VAT (<span id="t-vat-rate">20</span>%)</span>
|
|
97
|
-
<span id="t-vat" style="font-variant-numeric:tabular-nums;"
|
|
97
|
+
<span id="t-vat" style="font-variant-numeric:tabular-nums;">-</span>
|
|
98
98
|
</div>
|
|
99
99
|
<hr>
|
|
100
100
|
<div class="d-flex justify-content-between">
|
|
101
101
|
<strong>Total</strong>
|
|
102
|
-
<strong id="t-total" style="font-variant-numeric:tabular-nums;"
|
|
102
|
+
<strong id="t-total" style="font-variant-numeric:tabular-nums;">-</strong>
|
|
103
103
|
</div>
|
|
104
104
|
</div>
|
|
105
105
|
</div>
|
|
@@ -119,9 +119,9 @@
|
|
|
119
119
|
<div class="card-header"><strong>Currency</strong></div>
|
|
120
120
|
<div class="card-body">
|
|
121
121
|
<select id="f-currency" class="form-select">
|
|
122
|
-
<option value="GBP">GBP
|
|
123
|
-
<option value="EUR">EUR
|
|
124
|
-
<option value="USD">USD
|
|
122
|
+
<option value="GBP">GBP - British Pound</option>
|
|
123
|
+
<option value="EUR">EUR - Euro</option>
|
|
124
|
+
<option value="USD">USD - US Dollar</option>
|
|
125
125
|
</select>
|
|
126
126
|
</div>
|
|
127
127
|
</div>
|
|
@@ -8,19 +8,19 @@
|
|
|
8
8
|
<div class="grid grid-cols-4 gap-3 mb-4">
|
|
9
9
|
<div class="card"><div class="card-body">
|
|
10
10
|
<div class="text-muted small">Total invoices</div>
|
|
11
|
-
<div class="h3 mb-0" id="kpi-total"
|
|
11
|
+
<div class="h3 mb-0" id="kpi-total">-</div>
|
|
12
12
|
</div></div>
|
|
13
13
|
<div class="card"><div class="card-body">
|
|
14
14
|
<div class="text-muted small">Outstanding</div>
|
|
15
|
-
<div class="h3 mb-0" id="kpi-outstanding"
|
|
15
|
+
<div class="h3 mb-0" id="kpi-outstanding">-</div>
|
|
16
16
|
</div></div>
|
|
17
17
|
<div class="card"><div class="card-body">
|
|
18
18
|
<div class="text-muted small">Paid</div>
|
|
19
|
-
<div class="h3 mb-0" id="kpi-paid"
|
|
19
|
+
<div class="h3 mb-0" id="kpi-paid">-</div>
|
|
20
20
|
</div></div>
|
|
21
21
|
<div class="card"><div class="card-body">
|
|
22
22
|
<div class="text-muted small">Drafts</div>
|
|
23
|
-
<div class="h3 mb-0" id="kpi-drafts"
|
|
23
|
+
<div class="h3 mb-0" id="kpi-drafts">-</div>
|
|
24
24
|
</div></div>
|
|
25
25
|
</div>
|
|
26
26
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Invoice plugin
|
|
2
|
+
* Invoice plugin - editor view. Create or edit a single invoice.
|
|
3
3
|
*/
|
|
4
4
|
function escapeHtml(s) {
|
|
5
5
|
return String(s ?? '').replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"').replace(/'/g, ''');
|
|
@@ -77,7 +77,7 @@ export const editorView = {
|
|
|
77
77
|
while (el.firstChild) el.removeChild(el.firstChild);
|
|
78
78
|
const placeholder = document.createElement('option');
|
|
79
79
|
placeholder.value = '';
|
|
80
|
-
placeholder.textContent = '
|
|
80
|
+
placeholder.textContent = '- select -';
|
|
81
81
|
el.appendChild(placeholder);
|
|
82
82
|
for (const item of list) {
|
|
83
83
|
const opt = document.createElement('option');
|
|
@@ -151,7 +151,7 @@ export const editorView = {
|
|
|
151
151
|
const totalCell = document.createElement('td');
|
|
152
152
|
totalCell.style.cssText = 'text-align:right;font-variant-numeric:tabular-nums;';
|
|
153
153
|
totalCell.id = `line-total-${idx}`;
|
|
154
|
-
totalCell.textContent = '
|
|
154
|
+
totalCell.textContent = '-';
|
|
155
155
|
|
|
156
156
|
const removeBtn = document.createElement('button');
|
|
157
157
|
removeBtn.type = 'button';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Invoice plugin
|
|
2
|
+
* Invoice plugin - list view.
|
|
3
3
|
*/
|
|
4
4
|
function escapeHtml(s) {
|
|
5
5
|
return String(s ?? '').replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"').replace(/'/g, ''');
|
|
@@ -25,7 +25,7 @@ export const indexView = {
|
|
|
25
25
|
|
|
26
26
|
function receiverName(id) {
|
|
27
27
|
const r = receivers.find((x) => x.id === id);
|
|
28
|
-
if (!r) return '
|
|
28
|
+
if (!r) return '-';
|
|
29
29
|
return r.company || r.name || id;
|
|
30
30
|
}
|
|
31
31
|
|
|
@@ -50,13 +50,13 @@ export const indexView = {
|
|
|
50
50
|
columns: [
|
|
51
51
|
{
|
|
52
52
|
key: 'number', title: 'Number', sortable: true,
|
|
53
|
-
render: (v, row) => `<a href="#/plugins/invoice/edit/${escapeHtml(row.id)}" style="font-weight:600;">${escapeHtml(v || '
|
|
53
|
+
render: (v, row) => `<a href="#/plugins/invoice/edit/${escapeHtml(row.id)}" style="font-weight:600;">${escapeHtml(v || '-')}</a>`
|
|
54
54
|
},
|
|
55
55
|
{ key: 'receiverId', title: 'Billed To', sortable: false,
|
|
56
56
|
render: (v) => escapeHtml(receiverName(v))
|
|
57
57
|
},
|
|
58
58
|
{ key: 'issueDate', title: 'Issue Date', sortable: true,
|
|
59
|
-
render: (v) => v ? escapeHtml(D(v).format('DD MMM YYYY')) : '
|
|
59
|
+
render: (v) => v ? escapeHtml(D(v).format('DD MMM YYYY')) : '-'
|
|
60
60
|
},
|
|
61
61
|
{ key: 'totals', title: 'Total', sortable: false,
|
|
62
62
|
render: (totals, row) => `<span style="font-variant-numeric:tabular-nums;">${escapeHtml(fmt(totals?.total ?? 0, row.currency))}</span>`
|
|
@@ -25,11 +25,11 @@ export async function mountPartyView($container, config) {
|
|
|
25
25
|
}
|
|
26
26
|
];
|
|
27
27
|
if (config.fields.some((f) => f.key === 'company')) {
|
|
28
|
-
cols.push({ key: 'company', title: 'Company', sortable: true, render: (v) => escapeHtml(v || '
|
|
28
|
+
cols.push({ key: 'company', title: 'Company', sortable: true, render: (v) => escapeHtml(v || '-') });
|
|
29
29
|
}
|
|
30
30
|
cols.push(
|
|
31
|
-
{ key: 'email', title: 'Email', sortable: true, render: (v) => v ? `<a href="mailto:${escapeHtml(v)}">${escapeHtml(v)}</a>` : '
|
|
32
|
-
{ key: 'vatNumber', title: 'VAT', sortable: false, render: (v) => v ? `<code>${escapeHtml(v)}</code>` : '
|
|
31
|
+
{ key: 'email', title: 'Email', sortable: true, render: (v) => v ? `<a href="mailto:${escapeHtml(v)}">${escapeHtml(v)}</a>` : '-' },
|
|
32
|
+
{ key: 'vatNumber', title: 'VAT', sortable: false, render: (v) => v ? `<code>${escapeHtml(v)}</code>` : '-' },
|
|
33
33
|
{ key: 'id', title: 'Actions', sortable: false,
|
|
34
34
|
render: (id) => `<div style="display:flex;gap:.4rem;">
|
|
35
35
|
<button class="btn btn-sm btn-outline party-edit" data-id="${escapeHtml(id)}"><span data-icon="edit" data-icon-size="14"></span></button>
|
|
@@ -65,19 +65,19 @@
|
|
|
65
65
|
],
|
|
66
66
|
"views": {
|
|
67
67
|
"plugin-invoice-list": {
|
|
68
|
-
"entry": "invoice/admin/views/index.js?v=
|
|
68
|
+
"entry": "invoice/admin/views/index.js?v=9c70ea80",
|
|
69
69
|
"exportName": "indexView"
|
|
70
70
|
},
|
|
71
71
|
"plugin-invoice-editor": {
|
|
72
|
-
"entry": "invoice/admin/views/editor.js?v=
|
|
72
|
+
"entry": "invoice/admin/views/editor.js?v=e5e153c1",
|
|
73
73
|
"exportName": "editorView"
|
|
74
74
|
},
|
|
75
75
|
"plugin-invoice-issuers": {
|
|
76
|
-
"entry": "invoice/admin/views/issuers.js?v=
|
|
76
|
+
"entry": "invoice/admin/views/issuers.js?v=ac69a08a",
|
|
77
77
|
"exportName": "issuersView"
|
|
78
78
|
},
|
|
79
79
|
"plugin-invoice-receivers": {
|
|
80
|
-
"entry": "invoice/admin/views/receivers.js?v=
|
|
80
|
+
"entry": "invoice/admin/views/receivers.js?v=f016e1ff",
|
|
81
81
|
"exportName": "receiversView"
|
|
82
82
|
}
|
|
83
83
|
}
|
|
@@ -101,7 +101,7 @@ export const notesView = {
|
|
|
101
101
|
const ellipsis = v && v.length > 100 ? '\u2026' : '';
|
|
102
102
|
return preview
|
|
103
103
|
? `<span style="font-size:0.87rem;opacity:0.8;">${esc(preview)}${ellipsis}</span>`
|
|
104
|
-
: '<span style="opacity:0.35;"
|
|
104
|
+
: '<span style="opacity:0.35;">-</span>';
|
|
105
105
|
}
|
|
106
106
|
},
|
|
107
107
|
{
|
|
@@ -109,7 +109,7 @@ export const notesView = {
|
|
|
109
109
|
title: 'Categories',
|
|
110
110
|
sortable: false,
|
|
111
111
|
render: (v) => {
|
|
112
|
-
if (!Array.isArray(v) || v.length === 0) return '<span style="opacity:0.35;"
|
|
112
|
+
if (!Array.isArray(v) || v.length === 0) return '<span style="opacity:0.35;">-</span>';
|
|
113
113
|
return v.map((c) =>
|
|
114
114
|
`<span class="badge badge-outline notes-cat-tag" data-cat="${esc(c)}" `
|
|
115
115
|
+ `style="font-size:0.7rem;margin-right:2px;cursor:pointer;">${esc(c)}</span>`
|
|
@@ -122,7 +122,7 @@ export const notesView = {
|
|
|
122
122
|
sortable: true,
|
|
123
123
|
render: (v) => v
|
|
124
124
|
? `<span title="${esc(D(v).format('DD MMM YYYY HH:mm'))}">${esc(D(v).fromNow())}</span>`
|
|
125
|
-
: '
|
|
125
|
+
: '-'
|
|
126
126
|
},
|
|
127
127
|
{
|
|
128
128
|
key: 'id',
|
|
@@ -160,7 +160,7 @@ export const notesView = {
|
|
|
160
160
|
Domma.icons.scan(wrap);
|
|
161
161
|
}
|
|
162
162
|
|
|
163
|
-
// ---- Render category filter bar (DOM methods
|
|
163
|
+
// ---- Render category filter bar (DOM methods - no innerHTML with user data) ----
|
|
164
164
|
function renderCategoryFilter() {
|
|
165
165
|
catFilterEl.textContent = '';
|
|
166
166
|
|
package/plugins/notes/plugin.js
CHANGED
|
@@ -30,7 +30,7 @@ export default async function notesPlugin(fastify, options) {
|
|
|
30
30
|
return items;
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
-
/** GET /api/plugins/notes/items
|
|
33
|
+
/** GET /api/plugins/notes/items - supports ?q= and ?category= */
|
|
34
34
|
fastify.get('/items', { preHandler: [authenticate] }, async (request, reply) => {
|
|
35
35
|
const uid = userId(request);
|
|
36
36
|
const { q, category } = request.query;
|
|
@@ -43,13 +43,13 @@
|
|
|
43
43
|
<label class="form-label">Max results</label>
|
|
44
44
|
<input id="field-max-results" type="number" class="form-input" min="1" max="50"
|
|
45
45
|
placeholder="10">
|
|
46
|
-
<span class="form-hint">Maximum number of results to return per search (1
|
|
46
|
+
<span class="form-hint">Maximum number of results to return per search (1-50).</span>
|
|
47
47
|
</div>
|
|
48
48
|
<div class="col-6">
|
|
49
49
|
<label class="form-label">Minimum query length</label>
|
|
50
50
|
<input id="field-min-query-length" type="number" class="form-input" min="1" max="5"
|
|
51
51
|
placeholder="2">
|
|
52
|
-
<span class="form-hint">Minimum characters required before searching (1
|
|
52
|
+
<span class="form-hint">Minimum characters required before searching (1-5).</span>
|
|
53
53
|
</div>
|
|
54
54
|
</div>
|
|
55
55
|
|
|
@@ -58,7 +58,7 @@
|
|
|
58
58
|
<label class="form-label">Debounce delay (ms)</label>
|
|
59
59
|
<input id="field-debounce-ms" type="number" class="form-input" min="100" max="1000"
|
|
60
60
|
placeholder="300">
|
|
61
|
-
<span class="form-hint">Delay after typing stops before search fires (100
|
|
61
|
+
<span class="form-hint">Delay after typing stops before search fires (100-1000ms).</span>
|
|
62
62
|
</div>
|
|
63
63
|
</div>
|
|
64
64
|
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Site Search Plugin
|
|
2
|
+
* Site Search Plugin - Server
|
|
3
3
|
*
|
|
4
4
|
* Endpoints (prefix: /api/plugins/site-search):
|
|
5
|
-
* GET /search?q=term
|
|
6
|
-
* GET /settings
|
|
7
|
-
* PUT /settings
|
|
5
|
+
* GET /search?q=term - public: full-text search across all published pages
|
|
6
|
+
* GET /settings - public: return plugin settings (used by inject script)
|
|
7
|
+
* PUT /settings - admin-auth: save user overrides
|
|
8
8
|
*/
|
|
9
9
|
import {listPages} from '../../server/services/content.js';
|
|
10
10
|
import {getPluginSettings, savePluginState} from '../../server/services/plugins.js';
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<div class="survey-results-plugin">
|
|
2
2
|
|
|
3
3
|
<style>
|
|
4
|
-
/* Layout only + real Domma theme tokens
|
|
4
|
+
/* Layout only + real Domma theme tokens - surfaces use .card so the
|
|
5
5
|
active theme is always respected; no hardcoded colours. */
|
|
6
6
|
.survey-results-plugin .sr-head { display:flex; align-items:flex-start; gap:1rem; flex-wrap:wrap; margin-bottom:1.25rem; }
|
|
7
7
|
.survey-results-plugin .sr-head h2 { margin:0 0 0.25rem; }
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<div class="survey-editor-plugin">
|
|
2
2
|
|
|
3
3
|
<style>
|
|
4
|
-
/* Layout only
|
|
4
|
+
/* Layout only - colours/borders come from Domma's themed .card and
|
|
5
5
|
.form-input, so the active theme is always respected. */
|
|
6
6
|
.survey-editor-plugin .se-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 1rem 1.25rem; }
|
|
7
7
|
.survey-editor-plugin .se-span-2 { grid-column: 1 / -1; }
|
|
@@ -90,16 +90,16 @@
|
|
|
90
90
|
<div class="card-body">
|
|
91
91
|
<div class="se-grid">
|
|
92
92
|
<div class="se-field se-span-2">
|
|
93
|
-
<label for="f-intro">Intro (Markdown
|
|
93
|
+
<label for="f-intro">Intro (Markdown - shown above the form)</label>
|
|
94
94
|
<textarea id="f-intro" name="intro" class="form-input" placeholder="## Welcome Thanks for taking a moment to help us improve."></textarea>
|
|
95
95
|
</div>
|
|
96
96
|
<div class="se-field se-span-2">
|
|
97
|
-
<label for="f-footer">Footer (Markdown
|
|
97
|
+
<label for="f-footer">Footer (Markdown - shown below the form)</label>
|
|
98
98
|
<textarea id="f-footer" name="footer" class="form-input" placeholder="Questions? Contact us at hello@example.com"></textarea>
|
|
99
99
|
</div>
|
|
100
100
|
<div class="se-field se-span-2">
|
|
101
101
|
<label for="f-thankYou">Thank-you message (shown after submitting)</label>
|
|
102
|
-
<textarea id="f-thankYou" name="thankYou" class="form-input" placeholder="Thank you
|
|
102
|
+
<textarea id="f-thankYou" name="thankYou" class="form-input" placeholder="Thank you - your response has been recorded."></textarea>
|
|
103
103
|
</div>
|
|
104
104
|
</div>
|
|
105
105
|
</div>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Surveys plugin
|
|
2
|
+
* Surveys plugin - shared audience view.
|
|
3
3
|
*
|
|
4
4
|
* Manages survey contacts and audience groups, plus an import action that pulls
|
|
5
5
|
* the audience from the Contacts plugin.
|
|
@@ -90,15 +90,15 @@ export const audienceView = {
|
|
|
90
90
|
table = T.create($container.find('#audience-table').get(0), {
|
|
91
91
|
data: contacts,
|
|
92
92
|
columns: [
|
|
93
|
-
{key: 'name', title: 'Name', sortable: true, render: (v) => esc(v) || '
|
|
93
|
+
{key: 'name', title: 'Name', sortable: true, render: (v) => esc(v) || '-'},
|
|
94
94
|
{
|
|
95
95
|
key: 'email', title: 'Email', sortable: true,
|
|
96
|
-
render: (v) => v ? `<a href="mailto:${esc(v)}">${esc(v)}</a>` : '
|
|
96
|
+
render: (v) => v ? `<a href="mailto:${esc(v)}">${esc(v)}</a>` : '-'
|
|
97
97
|
},
|
|
98
98
|
{
|
|
99
99
|
key: 'groups', title: 'Groups', sortable: false,
|
|
100
100
|
render: (v) => {
|
|
101
|
-
if (!Array.isArray(v) || !v.length) return '
|
|
101
|
+
if (!Array.isArray(v) || !v.length) return '-';
|
|
102
102
|
return v.map((g) => `<span class="badge badge-primary" style="margin-right:2px;">${esc(g)}</span>`).join('');
|
|
103
103
|
}
|
|
104
104
|
},
|
|
@@ -152,7 +152,7 @@ export const audienceView = {
|
|
|
152
152
|
row.style.cssText = 'padding:0.4rem 0;border-bottom:1px solid var(--dm-border);';
|
|
153
153
|
const badge = document.createElement('span');
|
|
154
154
|
badge.className = 'badge badge-primary';
|
|
155
|
-
badge.textContent = g.name; // textContent
|
|
155
|
+
badge.textContent = g.name; // textContent - safe
|
|
156
156
|
row.appendChild(badge);
|
|
157
157
|
if (g.description) {
|
|
158
158
|
const desc = document.createElement('div');
|
|
@@ -177,7 +177,7 @@ export const audienceView = {
|
|
|
177
177
|
|
|
178
178
|
// Canonical F.create: (blueprintObject, defaultsObject, opts).renderTo(el).
|
|
179
179
|
// Blueprint is keyed by field name; placeholder/hint live under
|
|
180
|
-
// formConfig (proven shape
|
|
180
|
+
// formConfig (proven shape - collection-entries.js). We supply our own
|
|
181
181
|
// buttons (below) and read values from the DOM on save, so the modal's
|
|
182
182
|
// HTML sanitiser never strips an interactive submit button.
|
|
183
183
|
F.create(
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Surveys plugin
|
|
2
|
+
* Surveys plugin - results / statistics dashboard.
|
|
3
3
|
*
|
|
4
4
|
* Renders a response-rate summary, then per-question cards: distribution bars
|
|
5
5
|
* for choice/rating fields and a tidy answer list for free-text fields. Authed
|
|
@@ -76,7 +76,7 @@ export const resultsView = {
|
|
|
76
76
|
|
|
77
77
|
// ---- Header -------------------------------------------------------
|
|
78
78
|
const titleEl = root.querySelector('#results-title');
|
|
79
|
-
if (titleEl) titleEl.textContent = `Results
|
|
79
|
+
if (titleEl) titleEl.textContent = `Results - ${survey?.title || slug}`;
|
|
80
80
|
const subEl = root.querySelector('#results-sub');
|
|
81
81
|
if (subEl) {
|
|
82
82
|
const statusBadge = survey?.status ? ` · ${survey.status}` : '';
|
|
@@ -103,7 +103,7 @@ export const resultsView = {
|
|
|
103
103
|
let questionsHtml;
|
|
104
104
|
|
|
105
105
|
if (!completed && !fields.some((f) => answerCount(f) > 0)) {
|
|
106
|
-
questionsHtml = `<div class="card"><div class="card-body"><p class="sr-empty">No responses yet
|
|
106
|
+
questionsHtml = `<div class="card"><div class="card-body"><p class="sr-empty">No responses yet - once people complete the survey, their answers appear here.</p></div></div>`;
|
|
107
107
|
} else {
|
|
108
108
|
questionsHtml = fields.map((f, idx) => {
|
|
109
109
|
const n = answerCount(f);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Surveys plugin
|
|
2
|
+
* Surveys plugin - campaign list view.
|
|
3
3
|
*
|
|
4
4
|
* Lists survey campaigns in a Domma table with status/mode badges and
|
|
5
5
|
* row actions that navigate to the editor and results views.
|
|
@@ -144,7 +144,7 @@ export const surveysView = {
|
|
|
144
144
|
const audienceBtn = $container.find('#manage-audience').get(0);
|
|
145
145
|
if (audienceBtn) audienceBtn.addEventListener('click', () => { location.hash = '#/plugins/surveys/audience'; });
|
|
146
146
|
|
|
147
|
-
// Row-action delegation (proven pattern
|
|
147
|
+
// Row-action delegation (proven pattern - see admin/js/views/api-tokens.js).
|
|
148
148
|
// Unnamespaced + idempotent via .off()/.on(); namespaced delegation is
|
|
149
149
|
// broken in this codebase.
|
|
150
150
|
$container.off('click', '.btn-edit-survey').on('click', '.btn-edit-survey', function () {
|
|
@@ -12,7 +12,7 @@ import {registerPluginResource} from '../../server/services/permissionRegistry.j
|
|
|
12
12
|
|
|
13
13
|
const col = {createEntry, deleteEntry, getEntry, listEntries, updateEntry};
|
|
14
14
|
|
|
15
|
-
// Register the `surveys` permission family (idempotent
|
|
15
|
+
// Register the `surveys` permission family (idempotent - no-op if already present).
|
|
16
16
|
// Custom roles are NOT back-filled; admins grant it in the role editor (same gotcha
|
|
17
17
|
// as Menus/Projects).
|
|
18
18
|
registerPluginResource({
|
|
@@ -45,19 +45,19 @@
|
|
|
45
45
|
],
|
|
46
46
|
"views": {
|
|
47
47
|
"plugin-surveys": {
|
|
48
|
-
"entry": "surveys/admin/views/surveys.js?v=
|
|
48
|
+
"entry": "surveys/admin/views/surveys.js?v=86946458",
|
|
49
49
|
"exportName": "surveysView"
|
|
50
50
|
},
|
|
51
51
|
"plugin-survey-editor": {
|
|
52
|
-
"entry": "surveys/admin/views/survey-editor.js?v=
|
|
52
|
+
"entry": "surveys/admin/views/survey-editor.js?v=c6b3e1b7",
|
|
53
53
|
"exportName": "surveyEditorView"
|
|
54
54
|
},
|
|
55
55
|
"plugin-survey-audience": {
|
|
56
|
-
"entry": "surveys/admin/views/audience.js?v=
|
|
56
|
+
"entry": "surveys/admin/views/audience.js?v=6aca096f",
|
|
57
57
|
"exportName": "audienceView"
|
|
58
58
|
},
|
|
59
59
|
"plugin-survey-results": {
|
|
60
|
-
"entry": "surveys/admin/views/results.js?v=
|
|
60
|
+
"entry": "surveys/admin/views/results.js?v=97b8e1fd",
|
|
61
61
|
"exportName": "resultsView"
|
|
62
62
|
}
|
|
63
63
|
}
|