domma-cms 0.44.0 → 0.45.1
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 +752 -752
- package/admin/js/templates/form-editor.html +5 -5
- package/admin/js/templates/forms.html +17 -17
- package/admin/js/templates/login.html +5 -5
- package/admin/js/templates/menu-editor.html +10 -10
- package/admin/js/templates/my-profile.html +17 -17
- 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 +70 -70
- package/admin/js/templates/roles.html +10 -10
- 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 +44 -44
- package/bin/update.js +16 -16
- package/config/plugins.json +1 -1
- 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 +14 -14
- 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 +253 -253
- 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 +309 -309
- 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/navigation.js +42 -42
- 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 +167 -167
- 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 +199 -199
- package/server/services/userRoles.js +7 -7
- package/server/services/users.js +302 -302
- 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/bin/update.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
* Domma CMS
|
|
3
|
+
* Domma CMS - Project Update
|
|
4
4
|
* Usage: npx domma-cms update [--yes] [--no-backup] [--no-install] [--dry-run]
|
|
5
5
|
*
|
|
6
6
|
* Run from inside an existing Domma CMS project directory.
|
|
@@ -126,7 +126,7 @@ export default async function update(_positional, flags) {
|
|
|
126
126
|
console.log('');
|
|
127
127
|
console.log(' ┌──────────────────────────────────────────┐');
|
|
128
128
|
console.log(' │ │');
|
|
129
|
-
console.log(' │ Domma CMS
|
|
129
|
+
console.log(' │ Domma CMS - Project Updater │');
|
|
130
130
|
console.log(' │ │');
|
|
131
131
|
console.log(' └──────────────────────────────────────────┘');
|
|
132
132
|
console.log('');
|
|
@@ -146,14 +146,14 @@ export default async function update(_positional, flags) {
|
|
|
146
146
|
}
|
|
147
147
|
}
|
|
148
148
|
|
|
149
|
-
// Scaffolded projects are always private
|
|
149
|
+
// Scaffolded projects are always private - reject if not
|
|
150
150
|
try {
|
|
151
151
|
const cwdPkg = readJson(path.join(cwd, 'package.json'));
|
|
152
152
|
if (!cwdPkg.private) {
|
|
153
153
|
console.error(' ✗ This does not appear to be a scaffolded Domma CMS project (package.json is not private).\n');
|
|
154
154
|
process.exit(1);
|
|
155
155
|
}
|
|
156
|
-
} catch { /* malformed package.json
|
|
156
|
+
} catch { /* malformed package.json - let it proceed */
|
|
157
157
|
}
|
|
158
158
|
|
|
159
159
|
// -------------------------------------------------------------------------
|
|
@@ -169,7 +169,7 @@ export default async function update(_positional, flags) {
|
|
|
169
169
|
markerData = readJson(markerPath);
|
|
170
170
|
installedVersion = markerData.cmsVersion ?? 'unknown';
|
|
171
171
|
} catch {
|
|
172
|
-
warn('Could not read .domma marker
|
|
172
|
+
warn('Could not read .domma marker - treating as unknown version.');
|
|
173
173
|
}
|
|
174
174
|
} else {
|
|
175
175
|
warn('No .domma marker found. This project was scaffolded before version tracking was introduced.');
|
|
@@ -191,16 +191,16 @@ export default async function update(_positional, flags) {
|
|
|
191
191
|
}
|
|
192
192
|
|
|
193
193
|
// -------------------------------------------------------------------------
|
|
194
|
-
// 3. Dry-run mode
|
|
194
|
+
// 3. Dry-run mode - preview changes and exit
|
|
195
195
|
// -------------------------------------------------------------------------
|
|
196
196
|
|
|
197
197
|
if (isDryRun) {
|
|
198
|
-
info('Dry run
|
|
198
|
+
info('Dry run - no files will be modified.\n');
|
|
199
199
|
|
|
200
200
|
info('Upstream directories that would be replaced:');
|
|
201
201
|
for (const dir of UPSTREAM_DIRS) {
|
|
202
202
|
if (dir === 'public') {
|
|
203
|
-
info(` → ${dir}/ (upstream entries only
|
|
203
|
+
info(` → ${dir}/ (upstream entries only - site-added files are preserved)`);
|
|
204
204
|
} else {
|
|
205
205
|
info(` → ${dir}/`);
|
|
206
206
|
}
|
|
@@ -317,7 +317,7 @@ export default async function update(_positional, flags) {
|
|
|
317
317
|
let st = null;
|
|
318
318
|
try { st = lstatSync(destEntry); } catch { /* missing */ }
|
|
319
319
|
// A symlinked entry (e.g. a manager-shared public/js) is owned by
|
|
320
|
-
// whatever it points at
|
|
320
|
+
// whatever it points at - replacing it would silently fork it.
|
|
321
321
|
if (st && st.isSymbolicLink()) continue;
|
|
322
322
|
if (st) rmSync(destEntry, {recursive: true, force: true});
|
|
323
323
|
cpSync(path.join(src, entry), destEntry, {recursive: true});
|
|
@@ -355,7 +355,7 @@ export default async function update(_positional, flags) {
|
|
|
355
355
|
const isNew = !existsSync(dstPlugin);
|
|
356
356
|
|
|
357
357
|
if (isNew) {
|
|
358
|
-
// Brand-new plugin
|
|
358
|
+
// Brand-new plugin - copy wholesale, apply scaffold.reset
|
|
359
359
|
cpSync(srcPlugin, dstPlugin, {recursive: true});
|
|
360
360
|
try {
|
|
361
361
|
const manifest = readJson(path.join(dstPlugin, 'plugin.json'));
|
|
@@ -364,11 +364,11 @@ export default async function update(_positional, flags) {
|
|
|
364
364
|
mkdirSync(path.dirname(absPath), {recursive: true});
|
|
365
365
|
writeFileSync(absPath, content + '\n', 'utf8');
|
|
366
366
|
}
|
|
367
|
-
} catch { /* no manifest
|
|
367
|
+
} catch { /* no manifest - skip reset */
|
|
368
368
|
}
|
|
369
369
|
newPlugins.push(name);
|
|
370
370
|
} else {
|
|
371
|
-
// Existing plugin
|
|
371
|
+
// Existing plugin - identify paths to preserve
|
|
372
372
|
let preservePaths = new Set(['data']);
|
|
373
373
|
try {
|
|
374
374
|
const manifest = readJson(path.join(srcPlugin, 'plugin.json'));
|
|
@@ -390,7 +390,7 @@ export default async function update(_positional, flags) {
|
|
|
390
390
|
preserved.set(f, readFileSync(path.join(dstPlugin, f)));
|
|
391
391
|
}
|
|
392
392
|
} else {
|
|
393
|
-
// scaffold.reset entry
|
|
393
|
+
// scaffold.reset entry - preserve the single file
|
|
394
394
|
preserved.set(rel, readFileSync(abs));
|
|
395
395
|
}
|
|
396
396
|
}
|
|
@@ -448,7 +448,7 @@ export default async function update(_positional, flags) {
|
|
|
448
448
|
writeJson(dstPath, merged);
|
|
449
449
|
mergedKeys[cfgFile] = added;
|
|
450
450
|
}
|
|
451
|
-
} catch { /* malformed JSON
|
|
451
|
+
} catch { /* malformed JSON - skip */
|
|
452
452
|
}
|
|
453
453
|
}
|
|
454
454
|
done();
|
|
@@ -483,13 +483,13 @@ export default async function update(_positional, flags) {
|
|
|
483
483
|
projectPkg.scripts = newScripts;
|
|
484
484
|
|
|
485
485
|
writeFileSync(projectPkgPath, JSON.stringify(projectPkg, null, 2) + '\n', 'utf8');
|
|
486
|
-
} catch { /* malformed package.json
|
|
486
|
+
} catch { /* malformed package.json - skip */
|
|
487
487
|
}
|
|
488
488
|
done();
|
|
489
489
|
|
|
490
490
|
// -------------------------------------------------------------------------
|
|
491
491
|
// 11. Ensure runtime state is in .gitignore (self-heal existing installs)
|
|
492
|
-
// .domma-backups/ + analytics counters
|
|
492
|
+
// .domma-backups/ + analytics counters - tracking the latter resets
|
|
493
493
|
// live analytics on every checkout/pull/update (recurring bug).
|
|
494
494
|
// -------------------------------------------------------------------------
|
|
495
495
|
|
package/config/plugins.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "domma-cms",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.45.1",
|
|
4
4
|
"description": "File-based CMS powered by Domma and Fastify. Run npx domma-cms my-site to create a new project.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "server/server.js",
|
|
@@ -81,7 +81,7 @@
|
|
|
81
81
|
"@fastify/rate-limit": "^10.3.0",
|
|
82
82
|
"@fastify/static": "9.1.1",
|
|
83
83
|
"bcryptjs": "^3.0.3",
|
|
84
|
-
"domma-js": "0.39.
|
|
84
|
+
"domma-js": "0.39.2",
|
|
85
85
|
"dotenv": "^17.2.3",
|
|
86
86
|
"fastify": "5.8.5",
|
|
87
87
|
"gray-matter": "^4.0.3",
|
|
@@ -26,19 +26,19 @@
|
|
|
26
26
|
<div class="card">
|
|
27
27
|
<div class="card-body">
|
|
28
28
|
<div class="text-muted text-sm">Hits in range</div>
|
|
29
|
-
<div class="h2 mb-0" id="kpi-range"
|
|
29
|
+
<div class="h2 mb-0" id="kpi-range">-</div>
|
|
30
30
|
</div>
|
|
31
31
|
</div>
|
|
32
32
|
<div class="card">
|
|
33
33
|
<div class="card-body">
|
|
34
34
|
<div class="text-muted text-sm">Unique pages</div>
|
|
35
|
-
<div class="h2 mb-0" id="kpi-pages"
|
|
35
|
+
<div class="h2 mb-0" id="kpi-pages">-</div>
|
|
36
36
|
</div>
|
|
37
37
|
</div>
|
|
38
38
|
<div class="card">
|
|
39
39
|
<div class="card-body">
|
|
40
40
|
<div class="text-muted text-sm">Lifetime hits</div>
|
|
41
|
-
<div class="h2 mb-0" id="kpi-lifetime"
|
|
41
|
+
<div class="h2 mb-0" id="kpi-lifetime">-</div>
|
|
42
42
|
</div>
|
|
43
43
|
</div>
|
|
44
44
|
</div>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Analytics Plugin
|
|
2
|
+
* Analytics Plugin - Server
|
|
3
3
|
* Tracks page hits with per-day buckets so we can chart trends and
|
|
4
4
|
* filter by time range.
|
|
5
5
|
*
|
|
@@ -20,7 +20,7 @@ import {fileURLToPath} from 'url';
|
|
|
20
20
|
|
|
21
21
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
22
22
|
|
|
23
|
-
// Runtime counters live under data/
|
|
23
|
+
// Runtime counters live under data/ - the carve-out that BOTH update paths
|
|
24
24
|
// preserve (siteCmsUpdater's copyPlugin backs up data/; `domma-cms update`
|
|
25
25
|
// keeps 'data' in its preserve set). Pre-2.1 they sat in the plugin root and
|
|
26
26
|
// were overwritten by the manager's copy on every plugin push. On first boot
|
|
@@ -56,7 +56,7 @@ async function migrateToDataDir() {
|
|
|
56
56
|
await fs.mkdir(DATA_DIR, { recursive: true });
|
|
57
57
|
for (const name of ['lifetime.json', 'daily.json', 'journeys.json', 'stats.json']) {
|
|
58
58
|
const dest = path.join(DATA_DIR, name);
|
|
59
|
-
try { await fs.access(dest); continue; } catch { /* dest absent
|
|
59
|
+
try { await fs.access(dest); continue; } catch { /* dest absent - maybe move */ }
|
|
60
60
|
const oldPath = path.join(__dirname, name);
|
|
61
61
|
try {
|
|
62
62
|
await fs.access(oldPath);
|
|
@@ -355,7 +355,7 @@ export default async function analyticsPlugin(fastify, options) {
|
|
|
355
355
|
if (!daily[today]) daily[today] = {};
|
|
356
356
|
daily[today][normalised] = (daily[today][normalised] || 0) + 1;
|
|
357
357
|
|
|
358
|
-
// Journey event
|
|
358
|
+
// Journey event - only if sid is a string of reasonable shape, and under cap
|
|
359
359
|
const safeSid = typeof sid === 'string' && sid.length > 0 && sid.length <= 64 ? sid : null;
|
|
360
360
|
const safeRef = typeof ref === 'string' && ref.length <= 512 ? ref : '';
|
|
361
361
|
if (safeSid) {
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
var url = window.location.pathname;
|
|
12
12
|
|
|
13
|
-
// Session-scoped anonymous ID
|
|
13
|
+
// Session-scoped anonymous ID - clears with the tab. Cookie-less.
|
|
14
14
|
var sid = '';
|
|
15
15
|
try {
|
|
16
16
|
sid = sessionStorage.getItem('dm_sid') || '';
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
sessionStorage.setItem('dm_sid', sid);
|
|
22
22
|
}
|
|
23
23
|
} catch (e) {
|
|
24
|
-
// sessionStorage unavailable
|
|
24
|
+
// sessionStorage unavailable - proceed without sid
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
// Session dedup (one hit per URL per session)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
<!-- analytics: head injection (empty
|
|
1
|
+
<!-- analytics: head injection (empty - tracking is done via body script) -->
|
|
@@ -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</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">Published</div>
|
|
15
|
-
<div class="h3 mb-0" id="kpi-published"
|
|
15
|
+
<div class="h3 mb-0" id="kpi-published">-</div>
|
|
16
16
|
</div></div>
|
|
17
17
|
<div class="card"><div class="card-body">
|
|
18
18
|
<div class="text-muted small">Drafts</div>
|
|
19
|
-
<div class="h3 mb-0" id="kpi-drafts"
|
|
19
|
+
<div class="h3 mb-0" id="kpi-drafts">-</div>
|
|
20
20
|
</div></div>
|
|
21
21
|
<div class="card"><div class="card-body">
|
|
22
22
|
<div class="text-muted small">Scheduled</div>
|
|
23
|
-
<div class="h3 mb-0" id="kpi-scheduled"
|
|
23
|
+
<div class="h3 mb-0" id="kpi-scheduled">-</div>
|
|
24
24
|
</div></div>
|
|
25
25
|
</div>
|
|
26
26
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Blog posts overview
|
|
2
|
+
* Blog posts overview - KPI cards + filterable, sortable post table.
|
|
3
3
|
*
|
|
4
4
|
* @module blog/admin/views/blog
|
|
5
5
|
*/
|
|
@@ -61,7 +61,7 @@ export const blogView = {
|
|
|
61
61
|
sortable: false,
|
|
62
62
|
render: (val) => Array.isArray(val) && val.length
|
|
63
63
|
? val.map((id) => `<span class="badge badge-secondary mr-1">${escapeHtml(catNameById(id) || id)}</span>`).join('')
|
|
64
|
-
: '<span style="opacity:0.4;"
|
|
64
|
+
: '<span style="opacity:0.4;">-</span>'
|
|
65
65
|
},
|
|
66
66
|
{
|
|
67
67
|
key: 'publishedAt',
|
|
@@ -69,7 +69,7 @@ export const blogView = {
|
|
|
69
69
|
sortable: true,
|
|
70
70
|
render: (v) => v
|
|
71
71
|
? `<span title="${escapeHtml(D(v).format('DD MMM YYYY HH:mm'))}">${escapeHtml(D(v).format('DD MMM YYYY'))}</span>`
|
|
72
|
-
: '<span style="opacity:0.4;"
|
|
72
|
+
: '<span style="opacity:0.4;">-</span>'
|
|
73
73
|
},
|
|
74
74
|
{
|
|
75
75
|
key: 'id',
|
|
@@ -28,7 +28,7 @@ export const categoriesView = {
|
|
|
28
28
|
let descInput = null;
|
|
29
29
|
|
|
30
30
|
// ---------------------------------------------------------------
|
|
31
|
-
// Modal setup (built with DOM APIs
|
|
31
|
+
// Modal setup (built with DOM APIs - not innerHTML)
|
|
32
32
|
// ---------------------------------------------------------------
|
|
33
33
|
|
|
34
34
|
function buildModal() {
|
|
@@ -159,7 +159,7 @@ export const categoriesView = {
|
|
|
159
159
|
key: 'description',
|
|
160
160
|
title: 'Description',
|
|
161
161
|
sortable: false,
|
|
162
|
-
render: (v) => escapeHtml(v) || '<span style="opacity:0.4;"
|
|
162
|
+
render: (v) => escapeHtml(v) || '<span style="opacity:0.4;">-</span>'
|
|
163
163
|
},
|
|
164
164
|
{
|
|
165
165
|
key: 'id',
|
|
@@ -65,7 +65,7 @@ export const commentsView = {
|
|
|
65
65
|
sortable: false,
|
|
66
66
|
render: (v) => v
|
|
67
67
|
? `<a href="mailto:${escapeHtml(v)}">${escapeHtml(v)}</a>`
|
|
68
|
-
: '<span style="opacity:0.4;"
|
|
68
|
+
: '<span style="opacity:0.4;">-</span>'
|
|
69
69
|
},
|
|
70
70
|
{
|
|
71
71
|
key: 'body',
|
|
@@ -83,7 +83,7 @@ export const commentsView = {
|
|
|
83
83
|
sortable: false,
|
|
84
84
|
render: (v) => v
|
|
85
85
|
? `<a href="#/plugins/blog/posts/${escapeHtml(v)}">${escapeHtml(v)}</a>`
|
|
86
|
-
: '<span style="opacity:0.4;"
|
|
86
|
+
: '<span style="opacity:0.4;">-</span>'
|
|
87
87
|
},
|
|
88
88
|
{
|
|
89
89
|
key: 'createdAt',
|
|
@@ -91,7 +91,7 @@ export const commentsView = {
|
|
|
91
91
|
sortable: true,
|
|
92
92
|
render: (v) => v
|
|
93
93
|
? `<span title="${escapeHtml(D(v).format('DD MMM YYYY HH:mm'))}">${escapeHtml(D(v).fromNow())}</span>`
|
|
94
|
-
: '<span style="opacity:0.4;"
|
|
94
|
+
: '<span style="opacity:0.4;">-</span>'
|
|
95
95
|
},
|
|
96
96
|
{
|
|
97
97
|
key: 'id',
|
|
@@ -117,7 +117,7 @@ export const commentsView = {
|
|
|
117
117
|
emptyMessage: `No ${currentTab} comments.`
|
|
118
118
|
});
|
|
119
119
|
|
|
120
|
-
// Event delegation
|
|
120
|
+
// Event delegation - attach once
|
|
121
121
|
if (!tableEl.dataset.listenerAttached) {
|
|
122
122
|
tableEl.dataset.listenerAttached = '1';
|
|
123
123
|
tableEl.addEventListener('click', async (e) => {
|
package/plugins/blog/plugin.js
CHANGED
|
@@ -52,7 +52,7 @@ function toComment(entry) {
|
|
|
52
52
|
}
|
|
53
53
|
|
|
54
54
|
// ---------------------------------------------------------------------------
|
|
55
|
-
// Embed shortcodes
|
|
55
|
+
// Embed shortcodes - register `[blog-list]`, `[blog-grid]`, `[blog-featured]`,
|
|
56
56
|
// `[blog-categories]` so users can drop blog content into any page.
|
|
57
57
|
// ---------------------------------------------------------------------------
|
|
58
58
|
|
|
@@ -401,7 +401,7 @@ export default async function blogPlugin(fastify, options) {
|
|
|
401
401
|
return reply.send(comments);
|
|
402
402
|
});
|
|
403
403
|
|
|
404
|
-
/** POST /api/plugins/blog/comments
|
|
404
|
+
/** POST /api/plugins/blog/comments - public, no authenticate */
|
|
405
405
|
fastify.post('/comments', async (request, reply) => {
|
|
406
406
|
const body = request.body ?? {};
|
|
407
407
|
|
package/plugins/blog/plugin.json
CHANGED
|
@@ -72,19 +72,19 @@
|
|
|
72
72
|
],
|
|
73
73
|
"views": {
|
|
74
74
|
"plugin-blog-posts": {
|
|
75
|
-
"entry": "blog/admin/views/blog.js?v=
|
|
75
|
+
"entry": "blog/admin/views/blog.js?v=da5c1a46",
|
|
76
76
|
"exportName": "blogView"
|
|
77
77
|
},
|
|
78
78
|
"plugin-blog-post-editor": {
|
|
79
|
-
"entry": "blog/admin/views/post-editor.js?v=
|
|
79
|
+
"entry": "blog/admin/views/post-editor.js?v=d4d7a514",
|
|
80
80
|
"exportName": "postEditorView"
|
|
81
81
|
},
|
|
82
82
|
"plugin-blog-categories": {
|
|
83
|
-
"entry": "blog/admin/views/categories.js?v=
|
|
83
|
+
"entry": "blog/admin/views/categories.js?v=7e3946ca",
|
|
84
84
|
"exportName": "categoriesView"
|
|
85
85
|
},
|
|
86
86
|
"plugin-blog-comments": {
|
|
87
|
-
"entry": "blog/admin/views/comments.js?v=
|
|
87
|
+
"entry": "blog/admin/views/comments.js?v=ee2146e9",
|
|
88
88
|
"exportName": "commentsView"
|
|
89
89
|
},
|
|
90
90
|
"plugin-blog-settings": {
|
|
@@ -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
|
|