domma-cms 0.66.0 → 0.69.3

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 (221) hide show
  1. package/CLAUDE.md +64 -1
  2. package/README.md +14 -10
  3. package/admin/css/admin.css +1 -1
  4. package/admin/index.html +1 -0
  5. package/admin/js/api.js +1 -1
  6. package/admin/js/app.js +4 -4
  7. package/admin/js/lib/analytics-shape.js +1 -0
  8. package/admin/js/lib/contacts-arrange.js +1 -0
  9. package/admin/js/lib/help-popover.js +1 -0
  10. package/admin/js/lib/notes-arrange.js +1 -0
  11. package/admin/js/lib/plugin-accent.js +1 -1
  12. package/admin/js/lib/plugin-chrome.js +1 -1
  13. package/admin/js/lib/sidebar-grouping.js +1 -1
  14. package/admin/js/lib/sidebar-grouping.test.js +1 -1
  15. package/admin/js/lib/sidebar-renderer.js +3 -3
  16. package/admin/js/lib/todo-arrange.js +1 -0
  17. package/admin/js/lib/tool-kit.js +1 -0
  18. package/admin/js/templates/analytics.html +138 -0
  19. package/admin/js/templates/contacts.html +156 -0
  20. package/admin/js/templates/notes.html +137 -0
  21. package/admin/js/templates/plugin-marketplace.html +17 -4
  22. package/admin/js/templates/plugins.html +13 -7
  23. package/admin/js/templates/theme.html +38 -0
  24. package/admin/js/templates/todo.html +113 -0
  25. package/admin/js/views/analytics.js +62 -0
  26. package/admin/js/views/contacts.js +174 -0
  27. package/admin/js/views/index.js +1 -1
  28. package/admin/js/views/notes.js +104 -0
  29. package/admin/js/views/plugin-marketplace.js +1 -1
  30. package/admin/js/views/plugins.js +24 -22
  31. package/admin/js/views/theme.js +1 -1
  32. package/admin/js/views/todo.js +96 -0
  33. package/bin/cli.js +4 -10
  34. package/bin/lib/plugin-version.js +28 -0
  35. package/bin/update.js +14 -0
  36. package/config/menus/admin-sidebar.json +1 -1
  37. package/config/server.json +3 -1
  38. package/package.json +3 -7
  39. package/plugins/_lib/admin/mail/reader-view.js +20 -0
  40. package/plugins/_lib/mail/store.js +21 -1
  41. package/plugins/blog/CLAUDE.md +237 -0
  42. package/plugins/blog/admin/css/index.css +178 -1
  43. package/plugins/blog/admin/templates/blog.html +35 -53
  44. package/plugins/blog/admin/templates/categories.html +19 -6
  45. package/plugins/blog/admin/templates/comments.html +26 -9
  46. package/plugins/blog/admin/templates/post-editor.html +36 -13
  47. package/plugins/blog/admin/templates/settings.html +2 -11
  48. package/plugins/blog/admin/views/blog.js +288 -162
  49. package/plugins/blog/admin/views/categories.js +212 -201
  50. package/plugins/blog/admin/views/comments.js +207 -158
  51. package/plugins/blog/admin/views/kit.js +372 -0
  52. package/plugins/blog/admin/views/post-editor.js +269 -21
  53. package/plugins/blog/admin/views/settings-cog.js +56 -0
  54. package/plugins/blog/admin/views/settings.js +16 -86
  55. package/plugins/blog/blocks/blog-card-compact.css +17 -0
  56. package/plugins/blog/blocks/blog-card-compact.html +8 -0
  57. package/plugins/blog/blocks/blog-card-row.css +22 -0
  58. package/plugins/blog/blocks/blog-card-row.html +11 -0
  59. package/plugins/blog/blocks/blog-card.css +35 -0
  60. package/plugins/blog/blocks/blog-card.html +16 -0
  61. package/plugins/blog/blocks/blog-post-classic.css +15 -0
  62. package/plugins/blog/blocks/blog-post-classic.html +28 -0
  63. package/plugins/blog/blocks/blog-post-feature.css +20 -0
  64. package/plugins/blog/blocks/blog-post-feature.html +30 -0
  65. package/plugins/blog/blocks/blog-post-minimal.css +10 -0
  66. package/plugins/blog/blocks/blog-post-minimal.html +23 -0
  67. package/plugins/blog/blocks/blog-post-sidebar.css +22 -0
  68. package/plugins/blog/blocks/blog-post-sidebar.html +36 -0
  69. package/plugins/blog/collections/posts/schema.json +3 -1
  70. package/plugins/blog/config.js +6 -0
  71. package/plugins/blog/lib/layouts.js +304 -0
  72. package/plugins/blog/lib/page.js +158 -0
  73. package/plugins/blog/lib/render.js +119 -0
  74. package/plugins/blog/lib/samples.js +234 -0
  75. package/plugins/blog/plugin.js +225 -15
  76. package/plugins/blog/plugin.json +21 -81
  77. package/plugins/blog/plugin.public.js +115 -210
  78. package/plugins/blog/public/blog.css +177 -0
  79. package/plugins/blog/public/blog.js +586 -0
  80. package/plugins/blog/public/samples/aurora.svg +7 -0
  81. package/plugins/blog/public/samples/dusk.svg +7 -0
  82. package/plugins/blog/public/samples/ember.svg +7 -0
  83. package/plugins/blog/public/samples/harbour.svg +7 -0
  84. package/plugins/blog/public/samples/meadow.svg +7 -0
  85. package/plugins/blog/public/samples/rosewood.svg +7 -0
  86. package/plugins/blog/templates/index.html +9 -8
  87. package/plugins/blog/templates/post.html +4 -17
  88. package/plugins/blog/tests/layouts.test.js +97 -0
  89. package/plugins/blog/tests/public.test.js +71 -0
  90. package/plugins/free-tier.lock.json +74 -0
  91. package/plugins/mail-reader/CLAUDE.md +78 -0
  92. package/plugins/mail-reader/plugin.json +2 -1
  93. package/public/css/theme-switcher.css +1 -0
  94. package/public/js/analytics.js +1 -0
  95. package/public/js/theme-switcher.js +1 -0
  96. package/scripts/build.js +41 -2
  97. package/scripts/run-tests.mjs +92 -0
  98. package/{plugins/analytics/plugin.js → server/routes/api/analytics.js} +89 -41
  99. package/{plugins/contacts/plugin.js → server/routes/api/contacts.js} +71 -43
  100. package/server/routes/api/dashboard.js +8 -2
  101. package/{plugins/notes/plugin.js → server/routes/api/notes.js} +61 -17
  102. package/server/routes/api/plugin-marketplace.js +78 -23
  103. package/server/routes/api/plugins.js +148 -3
  104. package/server/routes/api/todo.js +178 -0
  105. package/server/server.js +61 -1
  106. package/server/services/collections.js +13 -3
  107. package/server/services/managerClient.js +72 -136
  108. package/server/services/markdown.js +54 -1
  109. package/server/services/permissionRegistry.js +50 -0
  110. package/server/services/pluginEntitlement.js +171 -0
  111. package/server/services/pluginEnvelope.js +242 -0
  112. package/server/services/pluginInstaller.js +246 -26
  113. package/server/services/pluginScaffold.js +5 -0
  114. package/server/services/plugins.js +117 -7
  115. package/server/services/presetCollections.js +344 -0
  116. package/server/services/promoted-plugins-migration.js +254 -0
  117. package/server/services/publicCors.js +46 -0
  118. package/server/services/renderer.js +83 -3
  119. package/server/services/sidebarBadges.js +37 -0
  120. package/server/services/siteGitignore.js +307 -0
  121. package/server/services/themeSettings.js +39 -0
  122. package/config/theme.json +0 -18
  123. package/plugins/analytics/admin/css/index.css +0 -1
  124. package/plugins/analytics/admin/templates/analytics.html +0 -70
  125. package/plugins/analytics/admin/views/analytics.js +0 -176
  126. package/plugins/analytics/config.js +0 -14
  127. package/plugins/analytics/plugin.json +0 -60
  128. package/plugins/analytics/public/inject-body.html +0 -60
  129. package/plugins/analytics/public/inject-head.html +0 -1
  130. package/plugins/blog/templates/author.html +0 -9
  131. package/plugins/blog/templates/category.html +0 -9
  132. package/plugins/blog/templates/tag.html +0 -9
  133. package/plugins/contacts/admin/templates/contacts.html +0 -254
  134. package/plugins/contacts/admin/views/contacts.js +0 -943
  135. package/plugins/contacts/collections/user-contact-groups/schema.json +0 -35
  136. package/plugins/contacts/collections/user-contacts/schema.json +0 -179
  137. package/plugins/contacts/config.js +0 -6
  138. package/plugins/contacts/plugin.json +0 -36
  139. package/plugins/demo-viewer/config.js +0 -4
  140. package/plugins/demo-viewer/plugin.js +0 -36
  141. package/plugins/demo-viewer/plugin.json +0 -9
  142. package/plugins/invoice/admin/css/index.css +0 -1
  143. package/plugins/invoice/admin/templates/editor.html +0 -220
  144. package/plugins/invoice/admin/templates/index.html +0 -173
  145. package/plugins/invoice/admin/templates/issuers.html +0 -2
  146. package/plugins/invoice/admin/templates/receivers.html +0 -2
  147. package/plugins/invoice/admin/views/contacts-source.js +0 -266
  148. package/plugins/invoice/admin/views/editor.js +0 -434
  149. package/plugins/invoice/admin/views/export.js +0 -199
  150. package/plugins/invoice/admin/views/help-content.js +0 -61
  151. package/plugins/invoice/admin/views/index.js +0 -643
  152. package/plugins/invoice/admin/views/issuers.js +0 -30
  153. package/plugins/invoice/admin/views/media.js +0 -172
  154. package/plugins/invoice/admin/views/party-view.js +0 -386
  155. package/plugins/invoice/admin/views/payments.js +0 -127
  156. package/plugins/invoice/admin/views/print.js +0 -130
  157. package/plugins/invoice/admin/views/receivers.js +0 -55
  158. package/plugins/invoice/admin/views/send.js +0 -212
  159. package/plugins/invoice/admin/views/settings.js +0 -594
  160. package/plugins/invoice/admin/views/view-lifecycle.js +0 -33
  161. package/plugins/invoice/collections/invoice-issuers/schema.json +0 -82
  162. package/plugins/invoice/collections/invoice-receivers/schema.json +0 -16
  163. package/plugins/invoice/collections/invoices/schema.json +0 -33
  164. package/plugins/invoice/config.js +0 -37
  165. package/plugins/invoice/pdf.js +0 -164
  166. package/plugins/invoice/plugin.js +0 -1456
  167. package/plugins/invoice/plugin.json +0 -86
  168. package/plugins/invoice/templates/_base.css +0 -1
  169. package/plugins/invoice/templates/classic-nologo.html +0 -100
  170. package/plugins/invoice/templates/classic.html +0 -91
  171. package/plugins/invoice/templates/invoice-print.html +0 -237
  172. package/plugins/invoice/templates/minimal.html +0 -99
  173. package/plugins/invoice/templates/modern-nologo.html +0 -114
  174. package/plugins/invoice/templates/modern.html +0 -113
  175. package/plugins/invoice/templates/templates.json +0 -11
  176. package/plugins/notes/admin/templates/notes.html +0 -83
  177. package/plugins/notes/admin/views/notes.js +0 -281
  178. package/plugins/notes/collections/user-notes/schema.json +0 -54
  179. package/plugins/notes/config.js +0 -6
  180. package/plugins/notes/plugin.json +0 -33
  181. package/plugins/surveys/admin/templates/audience.html +0 -47
  182. package/plugins/surveys/admin/templates/results.html +0 -58
  183. package/plugins/surveys/admin/templates/survey-editor.html +0 -141
  184. package/plugins/surveys/admin/templates/surveys.html +0 -25
  185. package/plugins/surveys/admin/views/audience.js +0 -301
  186. package/plugins/surveys/admin/views/results.js +0 -172
  187. package/plugins/surveys/admin/views/survey-editor.js +0 -211
  188. package/plugins/surveys/admin/views/surveys.js +0 -161
  189. package/plugins/surveys/collections/survey-contacts/schema.json +0 -13
  190. package/plugins/surveys/collections/survey-groups/schema.json +0 -11
  191. package/plugins/surveys/collections/survey-invites/schema.json +0 -16
  192. package/plugins/surveys/collections/surveys/schema.json +0 -23
  193. package/plugins/surveys/config.js +0 -8
  194. package/plugins/surveys/lib/audience.js +0 -37
  195. package/plugins/surveys/lib/campaigns.js +0 -43
  196. package/plugins/surveys/lib/ledger.js +0 -110
  197. package/plugins/surveys/lib/sending.js +0 -106
  198. package/plugins/surveys/lib/stats.js +0 -62
  199. package/plugins/surveys/lib/submit.js +0 -95
  200. package/plugins/surveys/lib/tokens.js +0 -28
  201. package/plugins/surveys/plugin.js +0 -174
  202. package/plugins/surveys/plugin.json +0 -65
  203. package/plugins/surveys/plugin.public.js +0 -149
  204. package/plugins/surveys/public/dist-shared.mjs +0 -1
  205. package/plugins/surveys/public/survey.css +0 -1
  206. package/plugins/surveys/public/survey.mjs +0 -1
  207. package/plugins/surveys/templates/survey-page.html +0 -63
  208. package/plugins/theme-switcher/admin/templates/theme-switcher.html +0 -86
  209. package/plugins/theme-switcher/admin/views/theme-switcher.js +0 -65
  210. package/plugins/theme-switcher/config.js +0 -10
  211. package/plugins/theme-switcher/plugin.js +0 -26
  212. package/plugins/theme-switcher/plugin.json +0 -74
  213. package/plugins/theme-switcher/public/inject-body.html +0 -153
  214. package/plugins/theme-switcher/public/inject-head.html +0 -260
  215. package/plugins/todo/admin/templates/todo.html +0 -158
  216. package/plugins/todo/admin/views/todo.js +0 -343
  217. package/plugins/todo/collections/todos/schema.json +0 -60
  218. package/plugins/todo/config.js +0 -7
  219. package/plugins/todo/plugin.js +0 -102
  220. package/plugins/todo/plugin.json +0 -33
  221. /package/{plugins/contacts/admin/css/index.css → admin/css/contacts.css} +0 -0
@@ -1,35 +0,0 @@
1
- {
2
- "slug": "contacts-groups",
3
- "title": "Contact Groups",
4
- "description": "Groups for the Contacts plugin.",
5
- "plugin": "contacts",
6
- "fields": [
7
- {
8
- "name": "name",
9
- "label": "Group Name",
10
- "type": "text",
11
- "required": true
12
- }
13
- ],
14
- "api": {
15
- "create": {
16
- "enabled": true,
17
- "access": "admin"
18
- },
19
- "read": {
20
- "enabled": true,
21
- "access": "admin"
22
- },
23
- "update": {
24
- "enabled": false,
25
- "access": "admin"
26
- },
27
- "delete": {
28
- "enabled": false,
29
- "access": "admin"
30
- }
31
- },
32
- "storage": {
33
- "adapter": "file"
34
- }
35
- }
@@ -1,179 +0,0 @@
1
- {
2
- "slug": "contacts-contacts",
3
- "title": "Contacts",
4
- "description": "Contacts managed by the Contacts plugin.",
5
- "plugin": "contacts",
6
- "fields": [
7
- {
8
- "name": "name",
9
- "label": "Name",
10
- "type": "text",
11
- "required": true
12
- },
13
- {
14
- "name": "email",
15
- "label": "Email",
16
- "type": "text",
17
- "required": false
18
- },
19
- {
20
- "name": "phone",
21
- "label": "Phone",
22
- "type": "text",
23
- "required": false
24
- },
25
- {
26
- "name": "groups",
27
- "label": "Groups",
28
- "type": "text",
29
- "required": false
30
- },
31
- {
32
- "name": "notes",
33
- "label": "Notes",
34
- "type": "textarea",
35
- "required": false
36
- },
37
- {
38
- "name": "favourite",
39
- "label": "Favourite",
40
- "type": "text",
41
- "required": false
42
- },
43
- {
44
- "name": "company",
45
- "label": "Company",
46
- "type": "text",
47
- "required": false
48
- },
49
- {
50
- "name": "jobTitle",
51
- "label": "Job Title",
52
- "type": "text",
53
- "required": false
54
- },
55
- {
56
- "name": "website",
57
- "label": "Website",
58
- "type": "text",
59
- "required": false
60
- },
61
- {
62
- "name": "vatNumber",
63
- "label": "VAT Number",
64
- "type": "text",
65
- "required": false
66
- },
67
- {
68
- "name": "companyNumber",
69
- "label": "Company Number",
70
- "type": "text",
71
- "required": false
72
- },
73
- {
74
- "name": "reference",
75
- "label": "Their Reference",
76
- "type": "text",
77
- "required": false
78
- },
79
- {
80
- "name": "billingEmail",
81
- "label": "Billing Email",
82
- "type": "text",
83
- "required": false
84
- },
85
- {
86
- "name": "paymentTerms",
87
- "label": "Payment Terms (days)",
88
- "type": "number",
89
- "required": false
90
- },
91
- {
92
- "name": "currency",
93
- "label": "Preferred Currency",
94
- "type": "text",
95
- "required": false
96
- },
97
- {
98
- "name": "addresses",
99
- "label": "Addresses",
100
- "type": "json",
101
- "required": false
102
- },
103
- {
104
- "name": "emails",
105
- "label": "Additional Emails",
106
- "type": "json",
107
- "required": false
108
- },
109
- {
110
- "name": "phones",
111
- "label": "Additional Phones",
112
- "type": "json",
113
- "required": false
114
- },
115
- {
116
- "name": "accountName",
117
- "label": "Account Name",
118
- "type": "text",
119
- "required": false
120
- },
121
- {
122
- "name": "bankName",
123
- "label": "Bank",
124
- "type": "text",
125
- "required": false
126
- },
127
- {
128
- "name": "sortCode",
129
- "label": "Sort Code",
130
- "type": "text",
131
- "required": false
132
- },
133
- {
134
- "name": "accountNumber",
135
- "label": "Account Number",
136
- "type": "text",
137
- "required": false
138
- },
139
- {
140
- "name": "iban",
141
- "label": "IBAN",
142
- "type": "text",
143
- "required": false
144
- },
145
- {
146
- "name": "bic",
147
- "label": "BIC / SWIFT",
148
- "type": "text",
149
- "required": false
150
- },
151
- {
152
- "name": "userId",
153
- "label": "User ID",
154
- "type": "text",
155
- "required": false
156
- }
157
- ],
158
- "api": {
159
- "create": {
160
- "enabled": true,
161
- "access": "admin"
162
- },
163
- "read": {
164
- "enabled": true,
165
- "access": "admin"
166
- },
167
- "update": {
168
- "enabled": false,
169
- "access": "admin"
170
- },
171
- "delete": {
172
- "enabled": false,
173
- "access": "admin"
174
- }
175
- },
176
- "storage": {
177
- "adapter": "file"
178
- }
179
- }
@@ -1,6 +0,0 @@
1
- export default {
2
- scope: 'user',
3
- maxPerUser: 5000,
4
- // storage.adapter: 'mongodb' uses config/connections.json; 'file' uses local JSON
5
- storage: {adapter: 'file'}
6
- };
@@ -1,36 +0,0 @@
1
- {
2
- "name": "contacts",
3
- "displayName": "Contacts",
4
- "version": "1.0.0",
5
- "description": "Contact manager with groups, favourites, search, and import/export.",
6
- "author": "Darryl Waterhouse",
7
- "date": "2026-03-24",
8
- "icon": "users",
9
- "admin": {
10
- "css": [
11
- "admin/css/index.css"
12
- ],
13
- "sidebar": [
14
- {
15
- "id": "contacts",
16
- "text": "Contacts",
17
- "icon": "users",
18
- "url": "#/plugins/contacts",
19
- "section": "#/plugins/contacts"
20
- }
21
- ],
22
- "routes": [
23
- {
24
- "path": "/plugins/contacts",
25
- "view": "plugin-contacts",
26
- "title": "Contacts - Domma CMS"
27
- }
28
- ],
29
- "views": {
30
- "plugin-contacts": {
31
- "entry": "contacts/admin/views/contacts.js?v=25e5dae4",
32
- "exportName": "contactsView"
33
- }
34
- }
35
- }
36
- }
@@ -1,4 +0,0 @@
1
- export default {
2
- basePath: '/public/demos',
3
- defaultHeight: '500px'
4
- };
@@ -1,36 +0,0 @@
1
- /**
2
- * Demo Viewer Plugin
3
- *
4
- * Registers a [demo] shortcode that embeds interactive Domma JS demos via iframe.
5
- *
6
- * Usage:
7
- * [demo src="elements/accordion" /]
8
- * [demo src="effects/ambient" height="600px" title="Ambient Effect" /]
9
- */
10
-
11
- export default async function demoViewerPlugin(fastify, opts) {
12
- const {hooks, settings} = opts;
13
- const basePath = settings.basePath || '/public/demos';
14
- const defaultHeight = settings.defaultHeight || '500px';
15
-
16
- hooks.registerShortcode('demo', (attrStr, body, context) => {
17
- const attrs = context.parseShortcodeAttrs(attrStr);
18
- const src = attrs.src || '';
19
- const height = attrs.height || defaultHeight;
20
- const title = attrs.title || '';
21
- const iframeSrc = `${basePath}/${src}.html`;
22
-
23
- const titleBar = title
24
- ? `<div class="demo-viewer-title">${context.escapeAttr(title)}</div>`
25
- : '';
26
-
27
- return `<div class="demo-viewer">${titleBar}<iframe src="${context.escapeAttr(iframeSrc)}" style="width:100%;height:${context.escapeAttr(height)};border:none;border-radius:var(--dm-radius, 8px);background:var(--dm-surface, #1a1a2e);" loading="lazy" allowfullscreen></iframe></div>`;
28
- }, {priority: 5});
29
-
30
- hooks.registerSanitizeRules({
31
- tags: ['iframe'],
32
- attributes: {
33
- iframe: ['src', 'height', 'width', 'style', 'loading', 'allowfullscreen']
34
- }
35
- });
36
- }
@@ -1,9 +0,0 @@
1
- {
2
- "name": "demo-viewer",
3
- "displayName": "Demo Viewer",
4
- "version": "1.0.0",
5
- "description": "Embeds interactive Domma JS demos via iframe shortcode",
6
- "author": "Domma",
7
- "date": "2026-03-31",
8
- "icon": "play-circle"
9
- }
@@ -1 +0,0 @@
1
- .invoice-hidden{display:none!important}.invoice-editor{align-items:flex-start}.invoice-editor-main{flex:1;min-width:0}.invoice-editor-side{width:300px;flex-shrink:0}@media(max-width:900px){.invoice-editor{flex-wrap:wrap}.invoice-editor-side{width:100%}}.invoice-num,.invoice-line-total{font-variant-numeric:tabular-nums}#line-items-table th{font-weight:600;font-size:.85em}#line-items-table td{vertical-align:middle}#line-items-table .form-input{padding:.35rem .5rem}#line-items-table th.invoice-col-desc{width:44%}#line-items-table th.invoice-col-qty{width:13%;text-align:right}#line-items-table th.invoice-col-price{width:17%;text-align:right}#line-items-table th.invoice-col-total{width:18%;text-align:right}#line-items-table th.invoice-col-remove{width:8%}#line-items-table td.invoice-line-total{text-align:right}#line-items-table input[type=number]{text-align:right;font-variant-numeric:tabular-nums}.li-remove{font-size:1.1em;line-height:1;padding:.15rem .5rem;min-width:1.9rem}.invoice-number-link{font-weight:600}.invoice-row-actions{display:flex;gap:.4rem}#invoices-table td .invoice-num{display:block;text-align:right}#invoices-table th:nth-child(5){text-align:right}#invoices-table th:last-child,#invoices-table td:last-child{text-align:right;width:3rem}.invoice-party-form{display:flex;flex-direction:column;gap:.75rem;padding:.5rem}.invoice-party-actions{display:flex;justify-content:flex-end;gap:.75rem;margin-top:.75rem}.stat-icon--danger{background:color-mix(in srgb,var(--dm-danger) 15%,transparent);color:var(--dm-danger)}.invoice-col-date,.invoice-col-method{width:22%}#payments-table th{font-weight:600;font-size:.85em}#payments-table td{vertical-align:middle}#payments-table td.invoice-line-total,#payments-table th.invoice-col-total{text-align:right}.invoice-balance strong{color:var(--dm-warning)}.invoice-balance.is-settled strong{color:var(--dm-success)}.invoice-toolbar .card-body{padding:.75rem 1rem}.invoice-toolbar-row{display:flex;align-items:center;gap:.5rem;flex-wrap:wrap}.invoice-toolbar-row--sub{margin-top:.6rem;padding-top:.6rem;border-top:1px solid var(--dm-border);font-size:.85rem}.invoice-toolbar-spacer{flex:1 1 auto}.invoice-search{position:relative;display:flex;align-items:center;flex:1 1 260px;min-width:200px}.invoice-search>[data-icon],.invoice-search>svg{position:absolute;left:.6rem;color:var(--dm-text-muted);pointer-events:none}.invoice-search .form-input{padding-left:2rem}.invoice-filter-narrow{width:auto;min-width:9rem}.invoice-field{display:flex;align-items:center;gap:.35rem;color:var(--dm-text-muted);white-space:nowrap}.invoice-field .form-input{width:auto}.invoice-check{display:flex;align-items:center;gap:.35rem;white-space:nowrap;cursor:pointer}.invoice-matches{white-space:nowrap}.invoice-hint{margin:-.25rem 0 .75rem}.invoice-row-menu{font-size:1.1rem;line-height:1;padding:.15rem .55rem}.invoice-settings{padding:.25rem}.invoice-settings .tab-panel{padding-top:.75rem}.invoice-settings-form{margin-bottom:.5rem}.invoice-settings-inline{display:flex;align-items:center;gap:.5rem;margin-bottom:.5rem}.invoice-settings-inline .form-label{margin:0;white-space:nowrap}.invoice-settings-inline .form-select{width:auto;min-width:12rem}.invoice-panel-head{display:flex;align-items:center;justify-content:flex-end;gap:.5rem;margin-bottom:.5rem;flex-wrap:wrap}.invoice-number-preview{font-family:var(--dm-font-mono, monospace);color:var(--dm-text-muted);margin-bottom:.25rem}.invoice-slideover-resizer{padding:0;position:absolute;left:0;top:0;bottom:0;width:6px;cursor:col-resize;z-index:2;background:transparent}.invoice-slideover-resizer:hover,.is-resizing>.invoice-slideover-resizer{background:var(--dm-primary);opacity:.7}.is-resizing{user-select:none;transition:none!important}.invoice-media-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(130px,1fr));gap:.6rem}.invoice-media-tile{display:flex;flex-direction:column;align-items:center;gap:.3rem;padding:.5rem;border:1px solid var(--dm-border);border-radius:var(--dm-radius-md, 8px);background:var(--dm-surface);cursor:pointer}.invoice-media-tile:hover{border-color:var(--dm-primary)}.invoice-media-tile img{max-width:100%;max-height:72px;object-fit:contain}.invoice-media-tile span{font-size:.7rem;color:var(--dm-text-muted);max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.invoice-logo-frame{display:flex;align-items:center;justify-content:center;min-height:110px;padding:.75rem;border:1px dashed var(--dm-border);border-radius:var(--dm-radius-md, 8px);background:var(--dm-background-alt)}.invoice-logo-preview{max-height:90px;max-width:100%;object-fit:contain}.invoice-media-row{display:flex;gap:.5rem;align-items:center}.invoice-media-row .form-input{flex:1 1 auto;min-width:0}.invoice-media-pick{flex:0 0 auto}.invoice-picker-filters{display:flex;gap:.5rem;margin-bottom:.6rem}.invoice-picker-filters .form-input{flex:1 1 auto}.invoice-picker-filters .form-select{width:auto}.invoice-picker-list{display:flex;flex-direction:column;gap:.2rem;max-height:60vh;overflow-y:auto}.invoice-picker-row{display:flex;align-items:baseline;gap:.6rem;width:100%;padding:.4rem .6rem;border:1px solid transparent;border-radius:var(--dm-radius-sm, 6px);background:transparent;text-align:left;cursor:pointer}.invoice-picker-row:hover{background:var(--dm-hover-bg, var(--dm-background-alt))}.invoice-picker-row.is-chosen{border-color:var(--dm-primary);background:color-mix(in srgb,var(--dm-primary) 10%,transparent)}.invoice-picker-name{flex:1 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.invoice-picker-mail{color:var(--dm-text-muted);font-size:.8rem;white-space:nowrap}.invoice-picker-count{flex:1 1 auto}.invoice-recipient-actions{display:flex;gap:.5rem;margin-top:.35rem}.invoice-receiver-row{display:flex;gap:.5rem;align-items:center}.invoice-receiver-row .form-select{flex:1 1 auto;min-width:0}#kpi-outstanding{font-size:1.4rem;line-height:1.2;font-variant-numeric:tabular-nums}@media(max-width:1500px){#kpi-outstanding{font-size:1.15rem}}.invoice-empty{margin:.75rem 0 0;padding:.85rem 1rem;border:1px dashed var(--dm-border);border-radius:var(--dm-radius-md, 8px);text-align:center}.dm-plugin-body .card-header--quiet{background:transparent;border-bottom:0;padding:.65rem 1rem 0;min-height:0}.dm-plugin-body .card-header--quiet+.card-body{padding-top:.5rem}.invoice-cap{display:inline-flex;align-items:center;font-size:.7rem;font-weight:700;letter-spacing:.07em;text-transform:uppercase;color:var(--dm-text-muted)}.invoice-kpi{cursor:pointer;transition:border-color .15s,transform .15s}.invoice-kpi:hover{border-color:var(--dm-primary);transform:translateY(-1px)}.invoice-kpi:active{transform:none}.invoice-setup{border-color:var(--dm-primary);margin-bottom:.75rem}.invoice-setup .card-body{display:flex;flex-direction:column;gap:.4rem}.invoice-setup p{margin:0}#btn-new-invoice:disabled{opacity:.5;cursor:not-allowed}.invoice-template-picker{display:grid;grid-template-columns:repeat(auto-fit,minmax(160px,1fr));gap:.5rem;margin-bottom:.75rem}.invoice-template-card{display:flex;flex-direction:column;align-items:flex-start;gap:.2rem;padding:.6rem .75rem;text-align:left;background:var(--dm-surface);border:1px solid var(--dm-border);border-radius:var(--dm-radius-md, 8px);cursor:pointer}.invoice-template-card:hover{border-color:var(--dm-primary)}.invoice-template-card.is-chosen{border-color:var(--dm-primary);background:color-mix(in srgb,var(--dm-primary) 10%,transparent)}.invoice-template-name{font-size:.85rem}.invoice-template-desc{font-size:.72rem;color:var(--dm-text-muted);line-height:1.35}.invoice-template-tag{font-size:.6rem;margin-top:.15rem}.invoice-template-preview{width:100%;height:60vh;min-height:320px;border:1px solid var(--dm-border);border-radius:var(--dm-radius-md, 8px);background:#fff}.invoice-template-preview.is-loading{opacity:.5}
@@ -1,220 +0,0 @@
1
- <!--
2
- Every value on this screen is bound. The view builds an observable per field
3
- and one computed per derived number, then calls M.applyBindings once - there
4
- is no render function, and no path by which the DOM and the totals can drift.
5
- The heading is removed by the admin's plugin shell (dedupeHeading), so nothing
6
- per-record is bound to it.
7
- -->
8
- <div class="view-header">
9
- <h1>Invoice</h1>
10
- <div class="view-header-actions">
11
- <button id="btn-print" type="button" class="btn btn-secondary" data-if="isEdit.value" data-on-click="openPrint">
12
- <span data-icon="printer"></span> Print
13
- </button>
14
- <button id="btn-pdf" type="button" class="btn btn-secondary" data-if="canPdf.value" data-on-click="downloadPdf">
15
- <span data-icon="download"></span> Download PDF
16
- </button>
17
- <button id="btn-send" type="button" class="btn btn-secondary" data-if="isEdit.value" data-on-click="sendInvoice">
18
- <span data-icon="mail"></span> Send
19
- </button>
20
- <button id="btn-save" type="button" class="btn btn-primary" data-on-click="save">
21
- <span data-icon="save"></span> Save
22
- </button>
23
- </div>
24
- </div>
25
-
26
- <div class="flex gap-4 invoice-editor">
27
-
28
- <div class="invoice-editor-main">
29
-
30
- <div class="card mb-3">
31
- <div class="card-body">
32
- <div class="grid grid-cols-2 gap-3">
33
- <div>
34
- <label class="form-label" for="f-number" data-help="Leave it blank and one is allocated when you save - per prefix, per year, never reused.">Invoice Number</label>
35
- <input id="f-number" type="text" class="form-input font-mono" data-model="number.value" placeholder="auto-generated">
36
- <div class="text-muted text-sm">Leave blank to auto-number</div>
37
- </div>
38
- <div>
39
- <label class="form-label" for="f-status">Status</label>
40
- <select id="f-status" class="form-select" data-model="status.value">
41
- <option value="draft">Draft</option>
42
- <option value="sent">Sent</option>
43
- <option value="paid">Paid</option>
44
- <option value="overdue">Overdue</option>
45
- <option value="cancelled">Cancelled</option>
46
- </select>
47
- </div>
48
- <div>
49
- <label class="form-label" for="f-issue-date">Issue Date</label>
50
- <!-- E.datePicker attaches to these; it writes ISO into the input and
51
- fires input/change, so data-model keeps working. -->
52
- <input id="f-issue-date" type="text" class="form-input" data-model="issueDate.value">
53
- </div>
54
- <div>
55
- <label class="form-label" for="f-due-date" data-help="Filled in from your payment terms. Past this date a sent invoice reports itself overdue.">Due Date</label>
56
- <input id="f-due-date" type="text" class="form-input" data-model="dueDate.value">
57
- </div>
58
- <div>
59
- <label class="form-label" for="f-issuer" data-help="Which of your businesses is raising this. Manage them under the cog, in Issuers.">Issuer</label>
60
- <select id="f-issuer" class="form-select"
61
- data-options="issuers.value"
62
- data-options-text="label"
63
- data-options-value="id"
64
- data-options-caption="'- select -'"
65
- data-model="issuerId.value"></select>
66
- </div>
67
- <div>
68
- <label class="form-label" for="f-receiver" data-help="Who you are billing. From Contacts imports one, with its company name, billing address and VAT number.">Receiver</label>
69
- <div class="invoice-receiver-row">
70
- <select id="f-receiver" class="form-select"
71
- data-options="receivers.value"
72
- data-options-text="label"
73
- data-options-value="id"
74
- data-options-caption="'- select -'"
75
- data-model="receiverId.value"></select>
76
- <button type="button" class="btn btn-sm btn-outline" data-if="canImport.value"
77
- data-on-click="importReceiver" title="Create a receiver from a contact">
78
- From Contacts
79
- </button>
80
- </div>
81
- </div>
82
- </div>
83
- </div>
84
- </div>
85
-
86
- <div class="card mb-3">
87
- <div class="card-header card-header--quiet flex justify-between items-center">
88
- <span class="invoice-cap" data-help="Prices are NET - VAT is applied once, to the total. Right-click a row to duplicate, move or remove it.">Line items</span>
89
- <button id="btn-add-line" type="button" class="btn btn-sm btn-outline" data-on-click="addLine">
90
- <span data-icon="plus"></span> Add Line
91
- </button>
92
- </div>
93
- <div class="card-body">
94
- <table class="table mb-0" id="line-items-table">
95
- <thead>
96
- <tr>
97
- <th class="invoice-col-desc">Description</th>
98
- <th class="invoice-col-qty">Qty</th>
99
- <th class="invoice-col-price">Unit (net)</th>
100
- <th class="invoice-col-total">Total</th>
101
- <th class="invoice-col-remove"></th>
102
- </tr>
103
- </thead>
104
- <!-- key=id is required: it is what keeps a row's own input focused
105
- and uncommitted while the row above it is deleted. -->
106
- <tbody id="line-items-body" data-each="lineItems.value key=id">
107
- <tr>
108
- <td><input type="text" class="form-input" data-model="description.value" placeholder="Description"></td>
109
- <td><input type="number" class="form-input" min="0" step="0.01" data-model="quantity.value"></td>
110
- <td><input type="number" class="form-input" min="0" step="0.01" data-model="unitPrice.value"></td>
111
- <td class="invoice-line-total" data-bind-text="lineTotalText.value"></td>
112
- <td>
113
- <button type="button" class="btn btn-sm btn-ghost li-remove" title="Remove"
114
- data-on-click="$parent.removeLine($data)">&times;</button>
115
- </td>
116
- </tr>
117
- </tbody>
118
- </table>
119
- </div>
120
- </div>
121
-
122
- <div class="card mb-3" data-if="isEdit.value">
123
- <div class="card-header card-header--quiet flex justify-between items-center">
124
- <span class="invoice-cap" data-help="Money received against this invoice. Settle the balance and the status moves to paid on its own.">Payments</span>
125
- <button id="btn-record-payment" type="button" class="btn btn-sm btn-outline" data-on-click="recordPayment">
126
- <span data-icon="plus"></span> Record Payment
127
- </button>
128
- </div>
129
- <div class="card-body">
130
- <table class="table mb-0" id="payments-table" data-if="hasPayments.value">
131
- <thead>
132
- <tr>
133
- <th class="invoice-col-date">Date</th>
134
- <th class="invoice-col-method">Method</th>
135
- <th>Reference</th>
136
- <th class="invoice-col-total">Amount</th>
137
- <th class="invoice-col-remove"></th>
138
- </tr>
139
- </thead>
140
- <tbody id="payments-body" data-each="payments.value key=id">
141
- <tr>
142
- <td data-bind-text="dateText"></td>
143
- <td data-bind-text="methodText"></td>
144
- <td data-bind-text="reference"></td>
145
- <td class="invoice-line-total" data-bind-text="amountText.value"></td>
146
- <td>
147
- <button type="button" class="btn btn-sm btn-ghost li-remove" title="Remove"
148
- data-on-click="$parent.removePayment($data)">&times;</button>
149
- </td>
150
- </tr>
151
- </tbody>
152
- </table>
153
- <p class="text-muted text-sm mb-0" data-if="noPayments.value">Nothing recorded against this invoice yet.</p>
154
- </div>
155
- </div>
156
-
157
- <div class="card mb-3">
158
- <div class="card-header card-header--quiet"><span class="invoice-cap" data-help="Printed on the invoice under the totals. Payment instructions, a purchase order number, a thank you.">Notes</span></div>
159
- <div class="card-body">
160
- <textarea id="f-notes" class="form-textarea" rows="3" data-model="notes.value"
161
- placeholder="Optional notes printed on the invoice"></textarea>
162
- </div>
163
- </div>
164
- </div>
165
-
166
- <div class="invoice-editor-side">
167
-
168
- <div class="card mb-3">
169
- <div class="card-header card-header--quiet"><span class="invoice-cap">Totals</span></div>
170
- <div class="card-body">
171
- <div class="flex justify-between mb-2">
172
- <span class="text-muted">Subtotal</span>
173
- <span class="invoice-num" data-bind-text="subtotalText.value"></span>
174
- </div>
175
- <div class="flex justify-between mb-2" id="vat-row" data-if="vatEnabled.value">
176
- <span class="text-muted">VAT (<span data-bind-text="vatRate.value"></span>%)</span>
177
- <span class="invoice-num" data-bind-text="vatText.value"></span>
178
- </div>
179
- <hr>
180
- <div class="flex justify-between">
181
- <strong>Total</strong>
182
- <strong class="invoice-num" data-bind-text="totalText.value"></strong>
183
- </div>
184
- <div class="flex justify-between mt-2" data-if="hasPayments.value">
185
- <span class="text-muted">Paid</span>
186
- <span class="invoice-num" data-bind-text="paidText.value"></span>
187
- </div>
188
- <div class="flex justify-between mt-1 invoice-balance" data-if="hasPayments.value" data-bind-class="settled.value && 'is-settled'">
189
- <strong data-bind-text="balanceLabel.value"></strong>
190
- <strong class="invoice-num" data-bind-text="balanceText.value"></strong>
191
- </div>
192
- </div>
193
- </div>
194
-
195
- <div class="card mb-3">
196
- <div class="card-header card-header--quiet"><span class="invoice-cap" data-help="One rate for the whole invoice, applied to the net total. Switch it off for a non-VAT invoice.">VAT</span></div>
197
- <div class="card-body">
198
- <!-- .form-checkbox is a WRAPPER in Domma; the class that styles the box
199
- itself is .form-check-input (appearance:none, primary fill, tick). -->
200
- <label class="form-check mb-2" for="f-vat-enabled">
201
- <input id="f-vat-enabled" type="checkbox" class="form-check-input" data-model="vatEnabled.value">
202
- <span class="form-check-label">Apply VAT</span>
203
- </label>
204
- <label class="form-label" for="f-vat-rate">Rate %</label>
205
- <input id="f-vat-rate" type="number" min="0" max="100" step="0.01" class="form-input" data-model="vatRate.value">
206
- </div>
207
- </div>
208
-
209
- <div class="card mb-3">
210
- <div class="card-header card-header--quiet"><span class="invoice-cap" data-help="Set per invoice. The default for new ones lives under the cog, in Defaults.">Currency</span></div>
211
- <div class="card-body">
212
- <select id="f-currency" class="form-select" data-model="currency.value">
213
- <option value="GBP">GBP - British Pound</option>
214
- <option value="EUR">EUR - Euro</option>
215
- <option value="USD">USD - US Dollar</option>
216
- </select>
217
- </div>
218
- </div>
219
- </div>
220
- </div>