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,60 +0,0 @@
1
- {
2
- "name": "analytics",
3
- "displayName": "Analytics",
4
- "version": "2.1.0",
5
- "description": "Page view analytics with daily trends, time-range filtering, top-pages table and CSV export. Honours Do-Not-Track and dedups per session.",
6
- "author": "Darryl Waterhouse",
7
- "date": "2026-05-04",
8
- "icon": "chart-bar",
9
- "admin": {
10
- "css": [
11
- "admin/css/index.css"
12
- ],
13
- "sidebar": [
14
- {
15
- "id": "analytics",
16
- "text": "Analytics",
17
- "icon": "chart-bar",
18
- "url": "#/plugins/analytics",
19
- "section": "#/plugins/analytics"
20
- }
21
- ],
22
- "routes": [
23
- {
24
- "path": "/plugins/analytics",
25
- "view": "plugin-analytics",
26
- "title": "Analytics - Domma CMS"
27
- }
28
- ],
29
- "views": {
30
- "plugin-analytics": {
31
- "entry": "analytics/admin/views/analytics.js?v=28fcef99",
32
- "exportName": "analyticsView"
33
- }
34
- }
35
- },
36
- "inject": {
37
- "head": "public/inject-head.html",
38
- "bodyEnd": "public/inject-body.html"
39
- },
40
- "scaffold": {
41
- "reset": [
42
- {
43
- "path": "data/lifetime.json",
44
- "content": "{}"
45
- },
46
- {
47
- "path": "data/daily.json",
48
- "content": "{}"
49
- },
50
- {
51
- "path": "data/journeys.json",
52
- "content": "{}"
53
- },
54
- {
55
- "path": "data/stats.json",
56
- "content": "{}"
57
- }
58
- ]
59
- }
60
- }
@@ -1,60 +0,0 @@
1
- <!-- analytics: page view tracker (DNT-aware, session-deduped) -->
2
- <script>
3
- (function () {
4
- if (typeof fetch !== 'function') return;
5
-
6
- // Preview renders are not visits. The editor previews a page into an
7
- // iframe where location.pathname is the srcdoc document rather than any
8
- // real URL, so tracking one both invents a page and inflates the counts.
9
- if (window.__CMS_PREVIEW__) return;
10
-
11
- var dnt = navigator.doNotTrack === '1'
12
- || window.doNotTrack === '1'
13
- || navigator.msDoNotTrack === '1';
14
- if (dnt) return;
15
-
16
- var url = window.location.pathname;
17
-
18
- // Session-scoped anonymous ID - clears with the tab. Cookie-less.
19
- var sid = '';
20
- try {
21
- sid = sessionStorage.getItem('dm_sid') || '';
22
- if (!sid) {
23
- sid = (crypto && crypto.randomUUID)
24
- ? crypto.randomUUID()
25
- : (Date.now().toString(36) + Math.random().toString(36).slice(2, 10));
26
- sessionStorage.setItem('dm_sid', sid);
27
- }
28
- } catch (e) {
29
- // sessionStorage unavailable - proceed without sid
30
- }
31
-
32
- // Session dedup (one hit per URL per session)
33
- try {
34
- var key = 'dm_analytics_seen';
35
- var seen = sessionStorage.getItem(key);
36
- var list = seen ? seen.split('|') : [];
37
- if (list.indexOf(url) !== -1) return;
38
- list.push(url);
39
- sessionStorage.setItem(key, list.join('|'));
40
- } catch (e) {
41
- // proceed anyway
42
- }
43
-
44
- // Strip query/fragment from referrer; only keep same-origin or external origin+path
45
- var ref = '';
46
- try {
47
- if (document.referrer) {
48
- var r = new URL(document.referrer);
49
- ref = r.origin === window.location.origin ? r.pathname : (r.origin + r.pathname);
50
- }
51
- } catch (e) { /* ignore */ }
52
-
53
- fetch('/api/plugins/analytics/hit', {
54
- method: 'POST',
55
- headers: { 'Content-Type': 'application/json' },
56
- keepalive: true,
57
- body: JSON.stringify({ url: url, sid: sid, ref: ref })
58
- }).catch(function () { /* silent */ });
59
- })();
60
- </script>
@@ -1 +0,0 @@
1
- <!-- analytics: head injection (empty - tracking is done via body script) -->
@@ -1,9 +0,0 @@
1
- <main class="container" style="padding: 2rem 0;">
2
- <h1 class="mb-4">{{archiveTitle}}</h1>
3
- <div id="blog-posts-list">
4
- {{posts}}
5
- </div>
6
- <div id="blog-pagination" class="mt-4">
7
- {{pagination}}
8
- </div>
9
- </main>
@@ -1,9 +0,0 @@
1
- <main class="container" style="padding: 2rem 0;">
2
- <h1 class="mb-4">{{archiveTitle}}</h1>
3
- <div id="blog-posts-list">
4
- {{posts}}
5
- </div>
6
- <div id="blog-pagination" class="mt-4">
7
- {{pagination}}
8
- </div>
9
- </main>
@@ -1,9 +0,0 @@
1
- <main class="container" style="padding: 2rem 0;">
2
- <h1 class="mb-4">{{archiveTitle}}</h1>
3
- <div id="blog-posts-list">
4
- {{posts}}
5
- </div>
6
- <div id="blog-pagination" class="mt-4">
7
- {{pagination}}
8
- </div>
9
- </main>
@@ -1,254 +0,0 @@
1
- <div class="contacts-plugin">
2
-
3
- <!-- Toolbar -->
4
- <div class="toolbar mb-3" style="display:flex;align-items:center;gap:0.5rem;flex-wrap:wrap;">
5
- <input id="contacts-search" type="text" class="form-input" placeholder="Search contacts…" style="width:220px;">
6
- <select id="group-filter" class="form-input" style="width:160px;">
7
- <option value="">All groups</option>
8
- </select>
9
- <button id="fav-toggle" class="btn btn-outline" title="Show favourites only">
10
- <span data-icon="star"></span>
11
- Favourites
12
- </button>
13
- <span style="flex:1;"></span>
14
- <button id="export-btn" class="btn btn-outline">
15
- <span data-icon="download"></span>
16
- Export
17
- </button>
18
- <button id="import-btn" class="btn btn-outline">
19
- <span data-icon="upload"></span>
20
- Import
21
- </button>
22
- <input id="import-file" type="file" accept=".json" style="display:none;">
23
- <button id="add-contact-btn" class="btn btn-primary">
24
- <span data-icon="plus"></span>
25
- Add Contact
26
- </button>
27
- </div>
28
-
29
- <!-- Stats bar -->
30
- <div id="contacts-stats" class="mb-3" style="display:flex;gap:1.5rem;font-size:0.875rem;color:var(--dm-text-muted);">
31
- <span>Total: <strong id="stat-total">0</strong></span>
32
- <span>Groups: <strong id="stat-groups">0</strong></span>
33
- <span>Favourites: <strong id="stat-favs">0</strong></span>
34
- <span id="bulk-actions" style="display:none;margin-left:auto;">
35
- <button id="bulk-delete-btn" class="btn btn-sm btn-danger">
36
- <span data-icon="trash"></span>
37
- Delete selected (<span id="selected-count">0</span>)
38
- </button>
39
- </span>
40
- </div>
41
-
42
- <!-- Groups panel -->
43
- <div id="groups-panel" class="card mb-3">
44
- <div class="card-header" style="display:flex;align-items:center;justify-content:space-between;cursor:pointer;" id="groups-panel-toggle">
45
- <span><span data-icon="folder"></span> <strong>Manage Groups</strong></span>
46
- <span data-icon="chevron-down" id="groups-chevron"></span>
47
- </div>
48
- <div id="groups-panel-body" class="card-body" style="display:none;">
49
- <div style="display:flex;gap:0.5rem;margin-bottom:1rem;">
50
- <input id="new-group-input" type="text" class="form-input" placeholder="New group name…"
51
- style="flex:1;">
52
- <button id="add-group-btn" class="btn btn-primary">
53
- <span data-icon="plus"></span>
54
- Add Group
55
- </button>
56
- </div>
57
- <div id="groups-list">
58
- <p class="text-muted">No groups yet. Add one above.</p>
59
- </div>
60
- </div>
61
- </div>
62
-
63
- <!-- Contact editor overlay -->
64
- <div id="contact-editor" class="card mb-3" style="display:none;">
65
- <div class="card-header" style="display:flex;align-items:center;justify-content:space-between;">
66
- <strong id="editor-title">Add Contact</strong>
67
- <button id="editor-cancel-btn" class="btn btn-sm btn-outline">Cancel</button>
68
- </div>
69
- <div class="card-body">
70
- <div class="grid grid-cols-2" style="gap:1rem;">
71
- <div class="form-group">
72
- <label for="contact-name" class="form-label">Name <span style="color:var(--dm-danger)">*</span></label>
73
- <input id="contact-name" type="text" class="form-input" placeholder="Full name">
74
- </div>
75
- <div class="form-group">
76
- <label for="contact-email" class="form-label">Email</label>
77
- <input id="contact-email" type="email" class="form-input" placeholder="email@example.com">
78
- </div>
79
- <div class="form-group">
80
- <label for="contact-phone" class="form-label">Phone</label>
81
- <input id="contact-phone" type="text" class="form-input" placeholder="+44 …">
82
- </div>
83
- <div class="form-group">
84
- <label class="form-label">Groups</label>
85
- <div id="contact-groups-display" style="min-height:38px;padding:0.375rem 0.75rem;border:1px solid var(--dm-border);border-radius:var(--dm-radius);cursor:pointer;display:flex;flex-wrap:wrap;gap:0.25rem;align-items:center;">
86
- <span class="text-muted" id="groups-placeholder">Select groups…</span>
87
- </div>
88
- <div id="groups-dropdown-list" class="card mt-1" style="display:none;max-height:180px;overflow-y:auto;position:absolute;z-index:100;min-width:200px;">
89
- <div class="card-body" id="groups-dropdown-items" style="padding:0.25rem;"></div>
90
- </div>
91
- </div>
92
- <div class="form-group" style="grid-column:1/-1;">
93
- <label for="contact-notes" class="form-label">Notes</label>
94
- <textarea id="contact-notes" class="form-input" rows="3" placeholder="Optional notes…"></textarea>
95
- </div>
96
- <div class="form-group">
97
- <label class="form-label" style="display:flex;align-items:center;gap:0.5rem;cursor:pointer;">
98
- <input id="contact-favourite" type="checkbox" class="form-check-input">
99
- Mark as favourite
100
- </label>
101
- </div>
102
- </div>
103
- <!-- Everything an invoice needs. Folded away because most contacts
104
- are a name and a number, and the day you need a VAT number you
105
- know where to look. -->
106
- <div class="contacts-billing">
107
- <button type="button" id="billing-toggle" class="contacts-billing-toggle">
108
- <span data-icon="chevron-right" id="billing-chevron"></span>
109
- <strong>Company, addresses &amp; invoicing</strong>
110
- <span class="text-muted text-sm" id="billing-summary"></span>
111
- </button>
112
-
113
- <div id="billing-body" class="contacts-billing-body">
114
- <div class="grid grid-cols-2 contacts-field-grid">
115
- <div class="form-group">
116
- <label for="contact-company" class="form-label">Company</label>
117
- <input id="contact-company" type="text" class="form-input" placeholder="Acme Ltd">
118
- </div>
119
- <div class="form-group">
120
- <label for="contact-job-title" class="form-label">Job Title</label>
121
- <input id="contact-job-title" type="text" class="form-input" placeholder="Finance Manager">
122
- </div>
123
- <div class="form-group">
124
- <label for="contact-website" class="form-label">Website</label>
125
- <input id="contact-website" type="text" class="form-input" placeholder="acme.co.uk">
126
- </div>
127
- <div class="form-group">
128
- <label for="contact-billing-email" class="form-label">Billing Email</label>
129
- <input id="contact-billing-email" type="email" class="form-input" placeholder="accounts@acme.co.uk">
130
- <div class="text-muted text-sm">Where invoices go, if not the email above.</div>
131
- </div>
132
- <div class="form-group">
133
- <label for="contact-vat-number" class="form-label">VAT Number</label>
134
- <input id="contact-vat-number" type="text" class="form-input font-mono" placeholder="GB123456789">
135
- </div>
136
- <div class="form-group">
137
- <label for="contact-company-number" class="form-label">Company Number</label>
138
- <input id="contact-company-number" type="text" class="form-input font-mono" placeholder="01234567">
139
- </div>
140
- <div class="form-group">
141
- <label for="contact-reference" class="form-label">Their Reference</label>
142
- <input id="contact-reference" type="text" class="form-input" placeholder="PO-4471">
143
- <div class="text-muted text-sm">Printed on their invoices, if they need one.</div>
144
- </div>
145
- <div class="form-group">
146
- <label for="contact-payment-terms" class="form-label">Payment Terms</label>
147
- <div class="flex items-center gap-2">
148
- <input id="contact-payment-terms" type="number" min="0" max="365" class="form-input contacts-terms-input" placeholder="30">
149
- <span class="text-muted text-sm">days</span>
150
- </div>
151
- </div>
152
- <div class="form-group">
153
- <label for="contact-currency" class="form-label">Preferred Currency</label>
154
- <select id="contact-currency" class="form-select">
155
- <option value="">Use the invoice default</option>
156
- <option value="GBP">GBP - British Pound</option>
157
- <option value="EUR">EUR - Euro</option>
158
- <option value="USD">USD - US Dollar</option>
159
- </select>
160
- </div>
161
- </div>
162
-
163
- <div class="contacts-repeater">
164
- <div class="contacts-repeater-head">
165
- <strong>Bank Details</strong>
166
- <span class="text-muted text-sm">Printed on an invoice so it can be paid.</span>
167
- </div>
168
- <div class="grid grid-cols-2 contacts-field-grid">
169
- <div class="form-group">
170
- <label for="contact-account-name" class="form-label">Account Name</label>
171
- <input id="contact-account-name" type="text" class="form-input" placeholder="Acme Ltd">
172
- </div>
173
- <div class="form-group">
174
- <label for="contact-bank-name" class="form-label">Bank</label>
175
- <input id="contact-bank-name" type="text" class="form-input" placeholder="Barclays">
176
- </div>
177
- <div class="form-group">
178
- <label for="contact-sort-code" class="form-label">Sort Code</label>
179
- <input id="contact-sort-code" type="text" class="form-input font-mono" placeholder="20-00-00" maxlength="8">
180
- </div>
181
- <div class="form-group">
182
- <label for="contact-account-number" class="form-label">Account Number</label>
183
- <input id="contact-account-number" type="text" class="form-input font-mono" placeholder="12345678" maxlength="8">
184
- </div>
185
- <div class="form-group">
186
- <label for="contact-iban" class="form-label">IBAN</label>
187
- <input id="contact-iban" type="text" class="form-input font-mono" placeholder="GB29 NWBK 6016 1331 9268 19">
188
- </div>
189
- <div class="form-group">
190
- <label for="contact-bic" class="form-label">BIC / SWIFT</label>
191
- <input id="contact-bic" type="text" class="form-input font-mono" placeholder="NWBKGB2L">
192
- </div>
193
- </div>
194
- </div>
195
-
196
- <div class="contacts-repeater" id="addresses-repeater">
197
- <div class="contacts-repeater-head">
198
- <strong>Addresses</strong>
199
- <button type="button" class="btn btn-sm btn-outline" id="add-address-btn">
200
- <span data-icon="plus"></span> Add Address
201
- </button>
202
- </div>
203
- <div id="addresses-list"></div>
204
- <p class="text-muted text-sm contacts-empty-row" id="addresses-empty">No address yet.</p>
205
- </div>
206
-
207
- <div class="contacts-repeater" id="emails-repeater">
208
- <div class="contacts-repeater-head">
209
- <strong>Additional Emails</strong>
210
- <button type="button" class="btn btn-sm btn-outline" id="add-email-btn">
211
- <span data-icon="plus"></span> Add Email
212
- </button>
213
- </div>
214
- <div id="emails-list"></div>
215
- <p class="text-muted text-sm contacts-empty-row" id="emails-empty">None.</p>
216
- </div>
217
-
218
- <div class="contacts-repeater" id="phones-repeater">
219
- <div class="contacts-repeater-head">
220
- <strong>Additional Phones</strong>
221
- <button type="button" class="btn btn-sm btn-outline" id="add-phone-btn">
222
- <span data-icon="plus"></span> Add Phone
223
- </button>
224
- </div>
225
- <div id="phones-list"></div>
226
- <p class="text-muted text-sm contacts-empty-row" id="phones-empty">None.</p>
227
- </div>
228
- </div>
229
- </div>
230
-
231
- <div style="display:flex;gap:0.5rem;margin-top:1rem;">
232
- <button id="editor-save-btn" class="btn btn-primary">
233
- <span data-icon="save"></span>
234
- <span id="editor-save-label">Save Contact</span>
235
- </button>
236
- <button id="editor-delete-btn" class="btn btn-danger" style="display:none;">
237
- <span data-icon="trash"></span>
238
- Delete
239
- </button>
240
- <button id="editor-cancel-btn2" class="btn btn-outline">Cancel</button>
241
- </div>
242
- </div>
243
- </div>
244
-
245
- <!-- Contacts table -->
246
- <div id="contacts-table"></div>
247
-
248
- <!-- Empty state -->
249
- <div id="contacts-empty" style="display:none;text-align:center;padding:3rem 1rem;color:var(--dm-text-muted);">
250
- <span data-icon="users" style="font-size:3rem;display:block;margin-bottom:1rem;"></span>
251
- <p>No contacts yet. Click <strong>Add Contact</strong> to get started.</p>
252
- </div>
253
-
254
- </div>