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.
Files changed (251) 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 +25 -25
  57. package/admin/js/templates/form-editor.html +5 -5
  58. package/admin/js/templates/login.html +5 -5
  59. package/admin/js/templates/menu-editor.html +10 -10
  60. package/admin/js/templates/navigation.html +11 -11
  61. package/admin/js/templates/notifications.html +1 -1
  62. package/admin/js/templates/page-editor.html +4 -4
  63. package/admin/js/templates/pro-docs.html +14 -14
  64. package/admin/js/templates/role-editor.html +1 -1
  65. package/admin/js/templates/settings.html +10 -10
  66. package/admin/js/templates/tutorials.html +3 -3
  67. package/admin/js/templates/view-editor.html +15 -15
  68. package/admin/js/views/action-editor.js +1 -1
  69. package/admin/js/views/actions-list.js +1 -1
  70. package/admin/js/views/api-endpoint-editor.js +2 -2
  71. package/admin/js/views/api-endpoints.js +2 -2
  72. package/admin/js/views/api-tokens.js +3 -3
  73. package/admin/js/views/blocks.js +2 -2
  74. package/admin/js/views/collection-editor.js +1 -1
  75. package/admin/js/views/collection-entries.js +1 -1
  76. package/admin/js/views/collections.js +1 -1
  77. package/admin/js/views/component-editor.js +1 -1
  78. package/admin/js/views/components.js +4 -4
  79. package/admin/js/views/dashboard/widgets/cache.js +1 -1
  80. package/admin/js/views/dashboard/widgets/journeys.js +1 -1
  81. package/admin/js/views/dashboard/widgets/kpi-strip.js +1 -1
  82. package/admin/js/views/form-editor.js +7 -7
  83. package/admin/js/views/form-submissions.js +1 -1
  84. package/admin/js/views/forms.js +1 -1
  85. package/admin/js/views/layouts.js +1 -1
  86. package/admin/js/views/login.js +2 -2
  87. package/admin/js/views/menu-editor.js +3 -3
  88. package/admin/js/views/menu-locations.js +1 -1
  89. package/admin/js/views/menus.js +3 -3
  90. package/admin/js/views/navigation.js +8 -8
  91. package/admin/js/views/page-editor.js +40 -40
  92. package/admin/js/views/pages.js +3 -3
  93. package/admin/js/views/plugin-code.js +2 -2
  94. package/admin/js/views/plugins.js +1 -1
  95. package/admin/js/views/project-settings.js +1 -1
  96. package/admin/js/views/projects.js +1 -1
  97. package/admin/js/views/roles.js +1 -1
  98. package/admin/js/views/user-editor.js +1 -1
  99. package/admin/js/views/users.js +3 -3
  100. package/admin/js/views/view-editor.js +1 -1
  101. package/admin/js/views/view-preview.js +1 -1
  102. package/admin/js/views/views-list.js +1 -1
  103. package/bin/cli.js +8 -8
  104. package/bin/lib/config-merge.js +5 -5
  105. package/bin/update.js +16 -16
  106. package/config/site.json +86 -86
  107. package/package.json +2 -2
  108. package/plugins/_template/admin/views/index.js +1 -1
  109. package/plugins/_template/config.js +1 -1
  110. package/plugins/_template/plugin.js +1 -1
  111. package/plugins/analytics/admin/templates/analytics.html +3 -3
  112. package/plugins/analytics/admin/views/analytics.js +1 -1
  113. package/plugins/analytics/config.js +1 -1
  114. package/plugins/analytics/plugin.js +4 -4
  115. package/plugins/analytics/plugin.json +1 -1
  116. package/plugins/analytics/public/inject-body.html +2 -2
  117. package/plugins/analytics/public/inject-head.html +1 -1
  118. package/plugins/blog/admin/templates/blog.html +4 -4
  119. package/plugins/blog/admin/views/blog.js +3 -3
  120. package/plugins/blog/admin/views/categories.js +2 -2
  121. package/plugins/blog/admin/views/comments.js +4 -4
  122. package/plugins/blog/admin/views/post-editor.js +1 -1
  123. package/plugins/blog/plugin.js +2 -2
  124. package/plugins/blog/plugin.json +4 -4
  125. package/plugins/contacts/admin/views/contacts.js +8 -8
  126. package/plugins/contacts/plugin.js +4 -4
  127. package/plugins/contacts/plugin.json +1 -1
  128. package/plugins/invoice/admin/templates/editor.html +6 -6
  129. package/plugins/invoice/admin/templates/index.html +4 -4
  130. package/plugins/invoice/admin/views/editor.js +3 -3
  131. package/plugins/invoice/admin/views/index.js +4 -4
  132. package/plugins/invoice/admin/views/issuers.js +1 -1
  133. package/plugins/invoice/admin/views/party-view.js +3 -3
  134. package/plugins/invoice/admin/views/receivers.js +1 -1
  135. package/plugins/invoice/config.js +1 -1
  136. package/plugins/invoice/plugin.js +1 -1
  137. package/plugins/invoice/plugin.json +4 -4
  138. package/plugins/notes/admin/views/notes.js +4 -4
  139. package/plugins/notes/plugin.js +1 -1
  140. package/plugins/notes/plugin.json +1 -1
  141. package/plugins/site-search/admin/templates/site-search.html +3 -3
  142. package/plugins/site-search/admin/views/site-search.js +1 -1
  143. package/plugins/site-search/config.js +1 -1
  144. package/plugins/site-search/plugin.js +4 -4
  145. package/plugins/site-search/plugin.json +1 -1
  146. package/plugins/surveys/admin/templates/results.html +1 -1
  147. package/plugins/surveys/admin/templates/survey-editor.html +4 -4
  148. package/plugins/surveys/admin/views/audience.js +6 -6
  149. package/plugins/surveys/admin/views/results.js +3 -3
  150. package/plugins/surveys/admin/views/survey-editor.js +1 -1
  151. package/plugins/surveys/admin/views/surveys.js +2 -2
  152. package/plugins/surveys/plugin.js +1 -1
  153. package/plugins/surveys/plugin.json +4 -4
  154. package/plugins/theme-switcher/admin/views/theme-switcher.js +1 -1
  155. package/plugins/theme-switcher/plugin.json +3 -3
  156. package/plugins/theme-switcher/public/inject-body.html +28 -28
  157. package/plugins/theme-switcher/public/inject-head.html +1 -1
  158. package/plugins/todo/plugin.js +1 -1
  159. package/public/css/forms.css +1 -1
  160. package/public/js/collection-browser.js +1 -1
  161. package/public/js/collection-context.js +1 -1
  162. package/public/js/form-logic-engine.js +1 -1
  163. package/public/js/forms.js +2 -1
  164. package/public/js/site.js +1 -1
  165. package/scripts/build.js +100 -6
  166. package/scripts/create-plugin.js +2 -2
  167. package/scripts/fresh.js +2 -2
  168. package/scripts/gen-instance-secret.js +1 -1
  169. package/scripts/pro.js +12 -12
  170. package/scripts/reset.js +2 -2
  171. package/scripts/setup.js +15 -15
  172. package/scripts/users.js +5 -5
  173. package/scripts/verify-assets.mjs +6 -6
  174. package/server/config.js +1 -1
  175. package/server/middleware/auth.js +11 -11
  176. package/server/middleware/managerAuth.js +3 -3
  177. package/server/routes/api/actions.js +4 -4
  178. package/server/routes/api/api-endpoints.js +7 -7
  179. package/server/routes/api/api-tokens.js +5 -5
  180. package/server/routes/api/auth.js +10 -10
  181. package/server/routes/api/blocks.js +3 -3
  182. package/server/routes/api/collections.js +18 -18
  183. package/server/routes/api/dashboard.js +5 -5
  184. package/server/routes/api/effects.js +2 -2
  185. package/server/routes/api/endpoints-public.js +5 -5
  186. package/server/routes/api/forms.js +47 -47
  187. package/server/routes/api/menu-locations.js +3 -3
  188. package/server/routes/api/menus.js +7 -7
  189. package/server/routes/api/notifications.js +2 -2
  190. package/server/routes/api/pages.js +14 -2
  191. package/server/routes/api/plugin-marketplace.js +3 -3
  192. package/server/routes/api/plugins.js +6 -6
  193. package/server/routes/api/projects.js +9 -9
  194. package/server/routes/api/scaffold.js +7 -7
  195. package/server/routes/api/settings.js +6 -6
  196. package/server/routes/api/sidebar.js +1 -1
  197. package/server/routes/api/users.js +5 -5
  198. package/server/routes/api/versions.js +1 -1
  199. package/server/routes/api/views.js +1 -1
  200. package/server/routes/docs-public.js +3 -3
  201. package/server/routes/public.js +14 -14
  202. package/server/server.js +34 -16
  203. package/server/services/actions.js +19 -19
  204. package/server/services/adapters/FileAdapter.js +2 -2
  205. package/server/services/adapters/MongoAdapter.js +5 -5
  206. package/server/services/apiEndpoints.js +15 -15
  207. package/server/services/apiTokens.js +7 -7
  208. package/server/services/blocks.js +13 -13
  209. package/server/services/cache/drivers/MemoryDriver.js +3 -3
  210. package/server/services/cache/drivers/NoneDriver.js +1 -1
  211. package/server/services/cache/index.js +5 -5
  212. package/server/services/collections.js +14 -14
  213. package/server/services/components.js +9 -9
  214. package/server/services/connectionManager.js +4 -4
  215. package/server/services/content.js +25 -2
  216. package/server/services/docs.js +14 -14
  217. package/server/services/email.js +1 -1
  218. package/server/services/filterEngine.js +6 -6
  219. package/server/services/forms.js +18 -10
  220. package/server/services/health.js +1 -1
  221. package/server/services/hooks.js +7 -7
  222. package/server/services/images.js +4 -4
  223. package/server/services/managerClient.js +1 -1
  224. package/server/services/markdown.js +79 -79
  225. package/server/services/menuRender.js +5 -5
  226. package/server/services/menus-migration.js +3 -3
  227. package/server/services/menus.js +31 -31
  228. package/server/services/permissionRegistry.js +1 -1
  229. package/server/services/pluginFiles.js +5 -5
  230. package/server/services/pluginInstaller.js +17 -17
  231. package/server/services/pluginScaffold.js +1 -1
  232. package/server/services/plugins.js +17 -17
  233. package/server/services/presetCollections.js +2 -2
  234. package/server/services/projects.js +35 -26
  235. package/server/services/recipes/contact-list.json +1 -1
  236. package/server/services/recipes/onboarding.json +8 -8
  237. package/server/services/references.js +8 -8
  238. package/server/services/renderer.js +9 -9
  239. package/server/services/roles.js +9 -9
  240. package/server/services/rowAccess.js +10 -10
  241. package/server/services/scaffolder.js +30 -30
  242. package/server/services/sidebar-migration.js +10 -10
  243. package/server/services/sitemap.js +1 -1
  244. package/server/services/userProfiles.js +4 -4
  245. package/server/services/userRoles.js +7 -7
  246. package/server/services/users.js +5 -5
  247. package/server/services/versions.js +3 -3
  248. package/server/services/viewPipeline.js +6 -6
  249. package/server/services/views.js +11 -11
  250. package/server/templates/page.html +5 -5
  251. 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 — 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
 
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Invoice plugin — editor view. Create or edit a single invoice.
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, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;').replace(/'/g, '&#39;');
@@ -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 = '— select —';
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 — list view.
2
+ * Invoice plugin - list view.
3
3
  */
4
4
  function escapeHtml(s) {
5
5
  return String(s ?? '').replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;').replace(/'/g, '&#39;');
@@ -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 || '—')}</a>`
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>`
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Invoice plugin — issuers CRUD view.
2
+ * Invoice plugin - issuers CRUD view.
3
3
  */
4
4
  import {mountPartyView} from './party-view.js?v=1';
5
5
 
@@ -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>
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Invoice plugin — receivers CRUD view.
2
+ * Invoice plugin - receivers CRUD view.
3
3
  */
4
4
  import {mountPartyView} from './party-view.js?v=1';
5
5
 
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Invoice Plugin — Default Configuration
2
+ * Invoice Plugin - Default Configuration
3
3
  *
4
4
  * numberPrefix used in generated invoice numbers (e.g. INV-2026-0001)
5
5
  * numberPadding minimum digits for the sequential portion
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Invoice Plugin — Server
2
+ * Invoice Plugin - Server
3
3
  *
4
4
  * Endpoints (mounted at /api/plugins/invoice):
5
5
  * GET /issuers admin list issuers
@@ -65,19 +65,19 @@
65
65
  ],
66
66
  "views": {
67
67
  "plugin-invoice-list": {
68
- "entry": "invoice/admin/views/index.js?v=a7963ce7",
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=897f0259",
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=ce720b84",
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=c4b34b26",
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;">\u2014</span>';
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;">\u2014</span>';
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
- : '\u2014'
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 — no innerHTML with user data) ----
163
+ // ---- Render category filter bar (DOM methods - no innerHTML with user data) ----
164
164
  function renderCategoryFilter() {
165
165
  catFilterEl.textContent = '';
166
166
 
@@ -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 — supports ?q= and ?category= */
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;
@@ -25,7 +25,7 @@
25
25
  ],
26
26
  "views": {
27
27
  "plugin-notes": {
28
- "entry": "notes/admin/views/notes.js?v=43194b9f",
28
+ "entry": "notes/admin/views/notes.js?v=1010cfca",
29
29
  "exportName": "notesView"
30
30
  }
31
31
  }
@@ -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–50).</span>
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–5).</span>
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–1000ms).</span>
61
+ <span class="form-hint">Delay after typing stops before search fires (100-1000ms).</span>
62
62
  </div>
63
63
  </div>
64
64
 
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Site Search Plugin — Admin Settings View
2
+ * Site Search Plugin - Admin Settings View
3
3
  */
4
4
  import {apiRequest} from '/admin/js/api.js';
5
5
 
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Site Search Plugin — Default Configuration
2
+ * Site Search Plugin - Default Configuration
3
3
  */
4
4
  export default {
5
5
  placeholder: 'Search pages...',
@@ -1,10 +1,10 @@
1
1
  /**
2
- * Site Search Plugin — Server
2
+ * Site Search Plugin - Server
3
3
  *
4
4
  * Endpoints (prefix: /api/plugins/site-search):
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
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';
@@ -28,7 +28,7 @@
28
28
  ],
29
29
  "views": {
30
30
  "plugin-site-search": {
31
- "entry": "site-search/admin/views/site-search.js?v=ff425f47",
31
+ "entry": "site-search/admin/views/site-search.js?v=0c7dd970",
32
32
  "exportName": "siteSearchView"
33
33
  }
34
34
  }
@@ -1,7 +1,7 @@
1
1
  <div class="survey-results-plugin">
2
2
 
3
3
  <style>
4
- /* Layout only + real Domma theme tokens — surfaces use .card so the
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 — colours/borders come from Domma's themed .card and
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 — shown above the form)</label>
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&#10;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 — shown below the form)</label>
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 — your response has been recorded."></textarea>
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 — shared audience view.
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 — safe
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 — collection-entries.js). We supply our own
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 — results / statistics dashboard.
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 — ${survey?.title || slug}`;
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 — once people complete the survey, their answers appear here.</p></div></div>`;
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 — campaign create/edit view.
2
+ * Surveys plugin - campaign create/edit view.
3
3
  *
4
4
  * The form is hand-built in the template as native inputs laid out in an
5
5
  * explicit CSS grid (guaranteed multi-column), so this view only populates the
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Surveys plugin — campaign list view.
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 — see admin/js/views/api-tokens.js).
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 — no-op if already present).
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=62e94063",
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=322a3ad4",
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=66eea1d5",
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=13ceda01",
60
+ "entry": "surveys/admin/views/results.js?v=97b8e1fd",
61
61
  "exportName": "resultsView"
62
62
  }
63
63
  }
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Theme Switcher — Admin Settings View
2
+ * Theme Switcher - Admin Settings View
3
3
  */
4
4
  import {apiRequest} from '/admin/js/api.js';
5
5