domma-cms 0.44.0 → 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.
Files changed (254) hide show
  1. package/CLAUDE.md +53 -53
  2. package/README.md +27 -27
  3. package/admin/css/admin.css +1 -1
  4. package/admin/dist/domma/domma-tools.css +3 -3
  5. package/admin/dist/domma/domma-tools.min.js +3 -3
  6. package/admin/index.html +2 -2
  7. package/admin/js/app.js +1 -1
  8. package/admin/js/lib/card-builder.js +2 -2
  9. package/admin/js/lib/crud-tutorial.js +1 -1
  10. package/admin/js/lib/effect-defs.js +1 -1
  11. package/admin/js/lib/effects-builder.js +1 -1
  12. package/admin/js/lib/image-editor.js +1 -1
  13. package/admin/js/lib/markdown-toolbar.js +2 -2
  14. package/admin/js/lib/project-context.js +1 -1
  15. package/admin/js/lib/project-quick-create.js +1 -1
  16. package/admin/js/lib/scribe-composer.js +1 -1
  17. package/admin/js/lib/server-restart.js +1 -1
  18. package/admin/js/lib/shortcode-context-menu.js +2 -2
  19. package/admin/js/lib/simple-editor.js +1 -1
  20. package/admin/js/lib/themes.js +1 -1
  21. package/admin/js/lib/timeline-builder.js +1 -1
  22. package/admin/js/templates/action-editor.html +8 -8
  23. package/admin/js/templates/api-endpoint-editor.html +7 -7
  24. package/admin/js/templates/api-endpoints.html +1 -1
  25. package/admin/js/templates/api-reference.html +1 -1
  26. package/admin/js/templates/block-editor.html +6 -6
  27. package/admin/js/templates/collection-editor.html +1 -1
  28. package/admin/js/templates/component-editor.html +2 -2
  29. package/admin/js/templates/dashboard/cache.html +3 -3
  30. package/admin/js/templates/dashboard/journeys.html +3 -3
  31. package/admin/js/templates/dashboard/kpi-strip.html +3 -3
  32. package/admin/js/templates/dashboard/top-pages.html +1 -1
  33. package/admin/js/templates/dashboard/traffic-chart.html +1 -1
  34. package/admin/js/templates/docs/api-actions.html +1 -1
  35. package/admin/js/templates/docs/api-collections.html +4 -4
  36. package/admin/js/templates/docs/api-pages.html +2 -2
  37. package/admin/js/templates/docs/api-plugins.html +1 -1
  38. package/admin/js/templates/docs/api-settings.html +2 -2
  39. package/admin/js/templates/docs/api-users.html +1 -1
  40. package/admin/js/templates/docs/components-howto.html +4 -4
  41. package/admin/js/templates/docs/components-reference.html +21 -21
  42. package/admin/js/templates/docs/components-rules.html +14 -14
  43. package/admin/js/templates/docs/components-walkthrough.html +13 -13
  44. package/admin/js/templates/docs/tutorial-crud.html +49 -49
  45. package/admin/js/templates/docs/tutorial-forms.html +4 -4
  46. package/admin/js/templates/docs/tutorial-plugin.html +10 -10
  47. package/admin/js/templates/docs/usage-actions.html +6 -6
  48. package/admin/js/templates/docs/usage-cta-shortcode.html +9 -9
  49. package/admin/js/templates/docs/usage-dconfig.html +4 -4
  50. package/admin/js/templates/docs/usage-navigation.html +1 -1
  51. package/admin/js/templates/docs/usage-pages.html +1 -1
  52. package/admin/js/templates/docs/usage-shortcodes.html +24 -24
  53. package/admin/js/templates/docs/usage-users-roles.html +3 -3
  54. package/admin/js/templates/docs/usage-views.html +4 -4
  55. package/admin/js/templates/documentation.html +1 -1
  56. package/admin/js/templates/effects.html +752 -752
  57. package/admin/js/templates/form-editor.html +5 -5
  58. package/admin/js/templates/forms.html +17 -17
  59. package/admin/js/templates/login.html +5 -5
  60. package/admin/js/templates/menu-editor.html +10 -10
  61. package/admin/js/templates/my-profile.html +17 -17
  62. package/admin/js/templates/navigation.html +11 -11
  63. package/admin/js/templates/notifications.html +1 -1
  64. package/admin/js/templates/page-editor.html +4 -4
  65. package/admin/js/templates/pro-docs.html +14 -14
  66. package/admin/js/templates/role-editor.html +70 -70
  67. package/admin/js/templates/roles.html +10 -10
  68. package/admin/js/templates/settings.html +10 -10
  69. package/admin/js/templates/tutorials.html +3 -3
  70. package/admin/js/templates/view-editor.html +15 -15
  71. package/admin/js/views/action-editor.js +1 -1
  72. package/admin/js/views/actions-list.js +1 -1
  73. package/admin/js/views/api-endpoint-editor.js +2 -2
  74. package/admin/js/views/api-endpoints.js +2 -2
  75. package/admin/js/views/api-tokens.js +3 -3
  76. package/admin/js/views/blocks.js +2 -2
  77. package/admin/js/views/collection-editor.js +1 -1
  78. package/admin/js/views/collection-entries.js +1 -1
  79. package/admin/js/views/collections.js +1 -1
  80. package/admin/js/views/component-editor.js +1 -1
  81. package/admin/js/views/components.js +4 -4
  82. package/admin/js/views/dashboard/widgets/cache.js +1 -1
  83. package/admin/js/views/dashboard/widgets/journeys.js +1 -1
  84. package/admin/js/views/dashboard/widgets/kpi-strip.js +1 -1
  85. package/admin/js/views/form-editor.js +7 -7
  86. package/admin/js/views/form-submissions.js +1 -1
  87. package/admin/js/views/forms.js +1 -1
  88. package/admin/js/views/layouts.js +1 -1
  89. package/admin/js/views/login.js +2 -2
  90. package/admin/js/views/menu-editor.js +3 -3
  91. package/admin/js/views/menu-locations.js +1 -1
  92. package/admin/js/views/menus.js +3 -3
  93. package/admin/js/views/navigation.js +8 -8
  94. package/admin/js/views/page-editor.js +40 -40
  95. package/admin/js/views/pages.js +3 -3
  96. package/admin/js/views/plugin-code.js +2 -2
  97. package/admin/js/views/plugins.js +1 -1
  98. package/admin/js/views/project-settings.js +1 -1
  99. package/admin/js/views/projects.js +1 -1
  100. package/admin/js/views/roles.js +1 -1
  101. package/admin/js/views/user-editor.js +1 -1
  102. package/admin/js/views/users.js +3 -3
  103. package/admin/js/views/view-editor.js +1 -1
  104. package/admin/js/views/view-preview.js +1 -1
  105. package/admin/js/views/views-list.js +1 -1
  106. package/bin/cli.js +8 -8
  107. package/bin/lib/config-merge.js +44 -44
  108. package/bin/update.js +16 -16
  109. package/config/plugins.json +1 -1
  110. package/package.json +2 -2
  111. package/plugins/_template/admin/views/index.js +1 -1
  112. package/plugins/_template/config.js +1 -1
  113. package/plugins/_template/plugin.js +1 -1
  114. package/plugins/analytics/admin/templates/analytics.html +3 -3
  115. package/plugins/analytics/admin/views/analytics.js +1 -1
  116. package/plugins/analytics/config.js +1 -1
  117. package/plugins/analytics/plugin.js +4 -4
  118. package/plugins/analytics/plugin.json +1 -1
  119. package/plugins/analytics/public/inject-body.html +2 -2
  120. package/plugins/analytics/public/inject-head.html +1 -1
  121. package/plugins/blog/admin/templates/blog.html +4 -4
  122. package/plugins/blog/admin/views/blog.js +3 -3
  123. package/plugins/blog/admin/views/categories.js +2 -2
  124. package/plugins/blog/admin/views/comments.js +4 -4
  125. package/plugins/blog/admin/views/post-editor.js +1 -1
  126. package/plugins/blog/plugin.js +2 -2
  127. package/plugins/blog/plugin.json +4 -4
  128. package/plugins/contacts/admin/views/contacts.js +8 -8
  129. package/plugins/contacts/plugin.js +4 -4
  130. package/plugins/contacts/plugin.json +1 -1
  131. package/plugins/invoice/admin/templates/editor.html +6 -6
  132. package/plugins/invoice/admin/templates/index.html +4 -4
  133. package/plugins/invoice/admin/views/editor.js +3 -3
  134. package/plugins/invoice/admin/views/index.js +4 -4
  135. package/plugins/invoice/admin/views/issuers.js +1 -1
  136. package/plugins/invoice/admin/views/party-view.js +3 -3
  137. package/plugins/invoice/admin/views/receivers.js +1 -1
  138. package/plugins/invoice/config.js +1 -1
  139. package/plugins/invoice/plugin.js +1 -1
  140. package/plugins/invoice/plugin.json +4 -4
  141. package/plugins/notes/admin/views/notes.js +4 -4
  142. package/plugins/notes/plugin.js +1 -1
  143. package/plugins/notes/plugin.json +1 -1
  144. package/plugins/site-search/admin/templates/site-search.html +3 -3
  145. package/plugins/site-search/admin/views/site-search.js +1 -1
  146. package/plugins/site-search/config.js +1 -1
  147. package/plugins/site-search/plugin.js +4 -4
  148. package/plugins/site-search/plugin.json +1 -1
  149. package/plugins/surveys/admin/templates/results.html +1 -1
  150. package/plugins/surveys/admin/templates/survey-editor.html +4 -4
  151. package/plugins/surveys/admin/views/audience.js +6 -6
  152. package/plugins/surveys/admin/views/results.js +3 -3
  153. package/plugins/surveys/admin/views/survey-editor.js +1 -1
  154. package/plugins/surveys/admin/views/surveys.js +2 -2
  155. package/plugins/surveys/plugin.js +1 -1
  156. package/plugins/surveys/plugin.json +4 -4
  157. package/plugins/theme-switcher/admin/views/theme-switcher.js +1 -1
  158. package/plugins/theme-switcher/plugin.json +3 -3
  159. package/plugins/theme-switcher/public/inject-body.html +28 -28
  160. package/plugins/theme-switcher/public/inject-head.html +1 -1
  161. package/plugins/todo/plugin.js +1 -1
  162. package/public/css/forms.css +1 -1
  163. package/public/js/collection-browser.js +1 -1
  164. package/public/js/collection-context.js +1 -1
  165. package/public/js/form-logic-engine.js +1 -1
  166. package/public/js/forms.js +2 -1
  167. package/public/js/site.js +1 -1
  168. package/scripts/build.js +14 -14
  169. package/scripts/create-plugin.js +2 -2
  170. package/scripts/fresh.js +2 -2
  171. package/scripts/gen-instance-secret.js +1 -1
  172. package/scripts/pro.js +12 -12
  173. package/scripts/reset.js +2 -2
  174. package/scripts/setup.js +15 -15
  175. package/scripts/users.js +5 -5
  176. package/scripts/verify-assets.mjs +6 -6
  177. package/server/config.js +1 -1
  178. package/server/middleware/auth.js +253 -253
  179. package/server/middleware/managerAuth.js +3 -3
  180. package/server/routes/api/actions.js +4 -4
  181. package/server/routes/api/api-endpoints.js +7 -7
  182. package/server/routes/api/api-tokens.js +5 -5
  183. package/server/routes/api/auth.js +309 -309
  184. package/server/routes/api/blocks.js +3 -3
  185. package/server/routes/api/collections.js +18 -18
  186. package/server/routes/api/dashboard.js +5 -5
  187. package/server/routes/api/effects.js +2 -2
  188. package/server/routes/api/endpoints-public.js +5 -5
  189. package/server/routes/api/forms.js +47 -47
  190. package/server/routes/api/menu-locations.js +3 -3
  191. package/server/routes/api/menus.js +7 -7
  192. package/server/routes/api/navigation.js +42 -42
  193. package/server/routes/api/notifications.js +2 -2
  194. package/server/routes/api/pages.js +14 -2
  195. package/server/routes/api/plugin-marketplace.js +3 -3
  196. package/server/routes/api/plugins.js +6 -6
  197. package/server/routes/api/projects.js +9 -9
  198. package/server/routes/api/scaffold.js +7 -7
  199. package/server/routes/api/settings.js +6 -6
  200. package/server/routes/api/sidebar.js +1 -1
  201. package/server/routes/api/users.js +5 -5
  202. package/server/routes/api/versions.js +1 -1
  203. package/server/routes/api/views.js +1 -1
  204. package/server/routes/docs-public.js +3 -3
  205. package/server/routes/public.js +14 -14
  206. package/server/server.js +34 -16
  207. package/server/services/actions.js +19 -19
  208. package/server/services/adapters/FileAdapter.js +2 -2
  209. package/server/services/adapters/MongoAdapter.js +5 -5
  210. package/server/services/apiEndpoints.js +15 -15
  211. package/server/services/apiTokens.js +7 -7
  212. package/server/services/blocks.js +13 -13
  213. package/server/services/cache/drivers/MemoryDriver.js +3 -3
  214. package/server/services/cache/drivers/NoneDriver.js +1 -1
  215. package/server/services/cache/index.js +5 -5
  216. package/server/services/collections.js +14 -14
  217. package/server/services/components.js +9 -9
  218. package/server/services/connectionManager.js +4 -4
  219. package/server/services/content.js +25 -2
  220. package/server/services/docs.js +14 -14
  221. package/server/services/email.js +167 -167
  222. package/server/services/filterEngine.js +6 -6
  223. package/server/services/forms.js +18 -10
  224. package/server/services/health.js +1 -1
  225. package/server/services/hooks.js +7 -7
  226. package/server/services/images.js +4 -4
  227. package/server/services/managerClient.js +1 -1
  228. package/server/services/markdown.js +79 -79
  229. package/server/services/menuRender.js +5 -5
  230. package/server/services/menus-migration.js +3 -3
  231. package/server/services/menus.js +31 -31
  232. package/server/services/permissionRegistry.js +1 -1
  233. package/server/services/pluginFiles.js +5 -5
  234. package/server/services/pluginInstaller.js +17 -17
  235. package/server/services/pluginScaffold.js +1 -1
  236. package/server/services/plugins.js +17 -17
  237. package/server/services/presetCollections.js +2 -2
  238. package/server/services/projects.js +35 -26
  239. package/server/services/recipes/contact-list.json +1 -1
  240. package/server/services/recipes/onboarding.json +8 -8
  241. package/server/services/references.js +8 -8
  242. package/server/services/renderer.js +9 -9
  243. package/server/services/roles.js +9 -9
  244. package/server/services/rowAccess.js +10 -10
  245. package/server/services/scaffolder.js +30 -30
  246. package/server/services/sidebar-migration.js +10 -10
  247. package/server/services/sitemap.js +1 -1
  248. package/server/services/userProfiles.js +199 -199
  249. package/server/services/userRoles.js +7 -7
  250. package/server/services/users.js +302 -302
  251. package/server/services/versions.js +3 -3
  252. package/server/services/viewPipeline.js +6 -6
  253. package/server/services/views.js +11 -11
  254. 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 — Project Update
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 — Project Updater │');
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 — reject if not
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 — let it proceed */
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 — treating as unknown version.');
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 — preview changes and exit
194
+ // 3. Dry-run mode - preview changes and exit
195
195
  // -------------------------------------------------------------------------
196
196
 
197
197
  if (isDryRun) {
198
- info('Dry run — no files will be modified.\n');
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 — site-added files are preserved)`);
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 — replacing it would silently fork it.
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 — copy wholesale, apply scaffold.reset
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 — skip reset */
367
+ } catch { /* no manifest - skip reset */
368
368
  }
369
369
  newPlugins.push(name);
370
370
  } else {
371
- // Existing plugin — identify paths to preserve
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 — preserve the single file
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 — skip */
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 — skip */
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 — tracking the latter resets
492
+ // .domma-backups/ + analytics counters - tracking the latter resets
493
493
  // live analytics on every checkout/pull/update (recurring bug).
494
494
  // -------------------------------------------------------------------------
495
495
 
@@ -20,7 +20,7 @@
20
20
  "settings": {}
21
21
  },
22
22
  "blog": {
23
- "enabled": true,
23
+ "enabled": false,
24
24
  "bundled": false,
25
25
  "settings": {}
26
26
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "domma-cms",
3
- "version": "0.44.0",
3
+ "version": "0.45.0",
4
4
  "description": "File-based CMS powered by Domma and Fastify. Run npx domma-cms my-site to create a new project.",
5
5
  "type": "module",
6
6
  "main": "server/server.js",
@@ -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.0",
84
+ "domma-js": "0.39.1",
85
85
  "dotenv": "^17.2.3",
86
86
  "fastify": "5.8.5",
87
87
  "gray-matter": "^4.0.3",
@@ -1,5 +1,5 @@
1
1
  /**
2
- * PLUGIN_DISPLAY_NAME — admin view.
2
+ * PLUGIN_DISPLAY_NAME - admin view.
3
3
  * Route: #/plugins/PLUGIN_SLUG
4
4
  */
5
5
  export const indexView = {
@@ -1,5 +1,5 @@
1
1
  /**
2
- * PLUGIN_DISPLAY_NAME plugin — default configuration.
2
+ * PLUGIN_DISPLAY_NAME plugin - default configuration.
3
3
  * Merged with user overrides from config/plugins.json.
4
4
  */
5
5
  export default {
@@ -1,5 +1,5 @@
1
1
  /**
2
- * PLUGIN_DISPLAY_NAME plugin — server-side routes and lifecycle hooks.
2
+ * PLUGIN_DISPLAY_NAME plugin - server-side routes and lifecycle hooks.
3
3
  */
4
4
 
5
5
  export default async function plugin(fastify, options) {
@@ -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">—</div>
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">—</div>
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">—</div>
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 — Admin View
2
+ * Analytics Plugin - Admin View
3
3
  * KPI cards, trend chart (Chart.js via CDN), filterable top-pages table, CSV export.
4
4
  */
5
5
  const CHART_CDN = 'https://cdn.jsdelivr.net/npm/chart.js@4.4.1/dist/chart.umd.min.js';
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Analytics Plugin — Default Configuration
2
+ * Analytics Plugin - Default Configuration
3
3
  *
4
4
  * excludeAdmin strip /admin pageviews before recording
5
5
  * excludePaths array of path prefixes to ignore (e.g. ['/api', '/healthz'])
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Analytics Plugin — Server
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/ — the carve-out that BOTH update paths
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 — maybe move */ }
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 — only if sid is a string of reasonable shape, and under cap
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) {
@@ -25,7 +25,7 @@
25
25
  ],
26
26
  "views": {
27
27
  "plugin-analytics": {
28
- "entry": "analytics/admin/views/analytics.js?v=2c26480d",
28
+ "entry": "analytics/admin/views/analytics.js?v=28fcef99",
29
29
  "exportName": "analyticsView"
30
30
  }
31
31
  }
@@ -10,7 +10,7 @@
10
10
 
11
11
  var url = window.location.pathname;
12
12
 
13
- // Session-scoped anonymous ID — clears with the tab. Cookie-less.
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 — proceed without sid
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 — tracking is done via body script) -->
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">—</div>
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">—</div>
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">—</div>
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">—</div>
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 — KPI cards + filterable, sortable post table.
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;">—</span>'
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;">—</span>'
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 — not innerHTML)
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;">—</span>'
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;">—</span>'
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;">—</span>'
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;">—</span>'
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 — attach once
120
+ // Event delegation - attach once
121
121
  if (!tableEl.dataset.listenerAttached) {
122
122
  tableEl.dataset.listenerAttached = '1';
123
123
  tableEl.addEventListener('click', async (e) => {
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Blog post editor view — create and edit posts.
2
+ * Blog post editor view - create and edit posts.
3
3
  *
4
4
  * @module blog/admin/views/post-editor
5
5
  */
@@ -52,7 +52,7 @@ function toComment(entry) {
52
52
  }
53
53
 
54
54
  // ---------------------------------------------------------------------------
55
- // Embed shortcodes — register `[blog-list]`, `[blog-grid]`, `[blog-featured]`,
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 — public, no authenticate */
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
 
@@ -72,19 +72,19 @@
72
72
  ],
73
73
  "views": {
74
74
  "plugin-blog-posts": {
75
- "entry": "blog/admin/views/blog.js?v=2722ac2e",
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=d3f2c327",
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=489c8dfe",
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=47c59ee8",
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 — values are escaped via esc()
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>` : '\u2014'
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) || '\u2014'
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 '\u2014';
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 — avoids namespaced delegation issues
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 — safe, no XSS
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 — safe
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 — safe
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 — supports ?q=, ?group=, ?favourites=true */
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 — must be before /:id */
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 — rename + cascade */
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 — delete + cascade */
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
 
@@ -25,7 +25,7 @@
25
25
  ],
26
26
  "views": {
27
27
  "plugin-contacts": {
28
- "entry": "contacts/admin/views/contacts.js?v=4d7ec7f7",
28
+ "entry": "contacts/admin/views/contacts.js?v=b7ed0c5c",
29
29
  "exportName": "contactsView"
30
30
  }
31
31
  }
@@ -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;">—</span>
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;">—</span>
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;">—</strong>
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 — British Pound</option>
123
- <option value="EUR">EUR — Euro</option>
124
- <option value="USD">USD — US Dollar</option>
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">—</div>
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">—</div>
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">—</div>
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">—</div>
23
+ <div class="h3 mb-0" id="kpi-drafts">-</div>
24
24
  </div></div>
25
25
  </div>
26
26