domma-cms 0.66.0 → 0.69.4

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 +94 -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 +310 -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,114 +0,0 @@
1
- <!--
2
- Modern, for an issuer with no logo: the trading name is the wordmark in
3
- the band, so the band is never a dark strip with a hole in it.
4
- -->
5
- <!DOCTYPE html>
6
- <html lang="en">
7
- <head>
8
- <meta charset="utf-8">
9
- <title>Invoice {{number}}</title>
10
- <style>
11
- {{baseCss}}
12
-
13
- /* Modern - a dark masthead band, the numbers doing the talking underneath. */
14
- .invoice { padding: 0 0 48px; }
15
- .band {
16
- background: #1b1f23;
17
- color: #fff;
18
- padding: 28px 56px;
19
- display: flex;
20
- justify-content: space-between;
21
- align-items: flex-start;
22
- gap: 24px;
23
- }
24
- .band .wordmark { font-size: 22px; font-weight: 700; letter-spacing: -0.3px; }
25
- .band .logo { max-height: 56px; margin: 0; }
26
- .band h1 { margin: 0; font-size: 24px; letter-spacing: 3px; font-weight: 600; }
27
- .band .number { font-size: 15px; opacity: .75; margin-top: 2px; }
28
- .band .meta { text-align: right; }
29
- .band .meta div { opacity: .85; font-size: 13px; }
30
- .band .status {
31
- background: rgba(255,255,255,.15);
32
- color: #fff;
33
- margin-top: 8px;
34
- }
35
- .top { display: none; }
36
- .body { padding: 32px 56px 0; }
37
- .parties { grid-template-columns: 1fr 1fr; }
38
- table.items th { border-bottom-color: #1b1f23; }
39
- .totals .grand th, .totals .grand td { border-top-color: #1b1f23; }
40
- </style>
41
- </head>
42
- <body>
43
-
44
- <div class="actions">
45
- <button onclick="window.print()">Print / Save as PDF</button>
46
- </div>
47
-
48
- <div class="invoice">
49
- <div class="band">
50
- <div>
51
- <div class="wordmark">{{issuerName}}</div>
52
- <div class="lines" style="opacity:.8;font-size:12px;margin-top:6px;">{{issuerEmail}}</div>
53
- </div>
54
- <div class="meta">
55
- <h1>INVOICE</h1>
56
- <div class="number">{{number}}</div>
57
- <div>{{issueDate}}</div>
58
- {{dueDate}}
59
- <div class="status">{{status}}</div>
60
- </div>
61
- </div>
62
-
63
- <div class="body">
64
- <div class="parties">
65
- <div class="party">
66
- <h3>From</h3>
67
- <div class="name">{{issuerName}}</div>
68
- <div class="lines">{{issuerAddress}}</div>
69
- <div class="lines">{{issuerPhone}}</div>
70
- {{issuerVat}}
71
- </div>
72
- <div class="party">
73
- <h3>Billed To</h3>
74
- <div class="name">{{receiverName}}</div>
75
- <div class="lines">{{receiverAddress}}</div>
76
- <div class="lines">{{receiverEmail}}</div>
77
- {{receiverVat}}
78
- </div>
79
- </div>
80
-
81
- <table class="items">
82
- <thead>
83
- <tr>
84
- <th>Description</th>
85
- <th class="num">Qty</th>
86
- <th class="num">Unit</th>
87
- <th class="num">Total</th>
88
- </tr>
89
- </thead>
90
- <tbody>
91
- {{items}}
92
- </tbody>
93
- </table>
94
-
95
- <div class="totals">
96
- <table>
97
- <tr><th>Subtotal</th><td class="num">{{subtotal}}</td></tr>
98
- {{vatRow}}
99
- <tr class="grand"><th>Total</th><td class="num">{{total}}</td></tr>
100
- {{paidRows}}
101
- </table>
102
- </div>
103
-
104
- {{notes}}
105
- {{bankDetails}}
106
-
107
- <div class="footer">{{footerNote}}</div>
108
- </div>
109
- </div>
110
-
111
- {{autoPrint}}
112
-
113
- </body>
114
- </html>
@@ -1,113 +0,0 @@
1
- <!--
2
- Modern - a dark masthead band carrying the logo and the invoice meta.
3
- -->
4
- <!DOCTYPE html>
5
- <html lang="en">
6
- <head>
7
- <meta charset="utf-8">
8
- <title>Invoice {{number}}</title>
9
- <style>
10
- {{baseCss}}
11
-
12
- /* Modern - a dark masthead band, the numbers doing the talking underneath. */
13
- .invoice { padding: 0 0 48px; }
14
- .band {
15
- background: #1b1f23;
16
- color: #fff;
17
- padding: 28px 56px;
18
- display: flex;
19
- justify-content: space-between;
20
- align-items: flex-start;
21
- gap: 24px;
22
- }
23
- .band .wordmark { font-size: 22px; font-weight: 700; letter-spacing: -0.3px; }
24
- .band .logo { max-height: 56px; margin: 0; }
25
- .band h1 { margin: 0; font-size: 24px; letter-spacing: 3px; font-weight: 600; }
26
- .band .number { font-size: 15px; opacity: .75; margin-top: 2px; }
27
- .band .meta { text-align: right; }
28
- .band .meta div { opacity: .85; font-size: 13px; }
29
- .band .status {
30
- background: rgba(255,255,255,.15);
31
- color: #fff;
32
- margin-top: 8px;
33
- }
34
- .top { display: none; }
35
- .body { padding: 32px 56px 0; }
36
- .parties { grid-template-columns: 1fr 1fr; }
37
- table.items th { border-bottom-color: #1b1f23; }
38
- .totals .grand th, .totals .grand td { border-top-color: #1b1f23; }
39
- </style>
40
- </head>
41
- <body>
42
-
43
- <div class="actions">
44
- <button onclick="window.print()">Print / Save as PDF</button>
45
- </div>
46
-
47
- <div class="invoice">
48
- <div class="band">
49
- <div>
50
- {{issuerLogo}}
51
- <div class="lines" style="opacity:.8;font-size:12px;margin-top:6px;">{{issuerEmail}}</div>
52
- </div>
53
- <div class="meta">
54
- <h1>INVOICE</h1>
55
- <div class="number">{{number}}</div>
56
- <div>{{issueDate}}</div>
57
- {{dueDate}}
58
- <div class="status">{{status}}</div>
59
- </div>
60
- </div>
61
-
62
- <div class="body">
63
- <div class="parties">
64
- <div class="party">
65
- <h3>From</h3>
66
- <div class="name">{{issuerName}}</div>
67
- <div class="lines">{{issuerAddress}}</div>
68
- <div class="lines">{{issuerPhone}}</div>
69
- {{issuerVat}}
70
- </div>
71
- <div class="party">
72
- <h3>Billed To</h3>
73
- <div class="name">{{receiverName}}</div>
74
- <div class="lines">{{receiverAddress}}</div>
75
- <div class="lines">{{receiverEmail}}</div>
76
- {{receiverVat}}
77
- </div>
78
- </div>
79
-
80
- <table class="items">
81
- <thead>
82
- <tr>
83
- <th>Description</th>
84
- <th class="num">Qty</th>
85
- <th class="num">Unit</th>
86
- <th class="num">Total</th>
87
- </tr>
88
- </thead>
89
- <tbody>
90
- {{items}}
91
- </tbody>
92
- </table>
93
-
94
- <div class="totals">
95
- <table>
96
- <tr><th>Subtotal</th><td class="num">{{subtotal}}</td></tr>
97
- {{vatRow}}
98
- <tr class="grand"><th>Total</th><td class="num">{{total}}</td></tr>
99
- {{paidRows}}
100
- </table>
101
- </div>
102
-
103
- {{notes}}
104
- {{bankDetails}}
105
-
106
- <div class="footer">{{footerNote}}</div>
107
- </div>
108
- </div>
109
-
110
- {{autoPrint}}
111
-
112
- </body>
113
- </html>
@@ -1,11 +0,0 @@
1
- {
2
- "_comment": "The templates offered in Settings > Templates. A site can add its own: drop <id>.html (and optionally <id>-nologo.html) beside this file and add an entry. `nologo: false` means the style has nowhere to put a logo, so it is used as-is either way.",
3
- "templates": [
4
- { "id": "classic", "name": "Classic", "nologo": true,
5
- "description": "Logo top left, invoice details top right. The traditional letterhead layout." },
6
- { "id": "modern", "name": "Modern", "nologo": true,
7
- "description": "A dark masthead band across the top carrying the logo and the invoice number." },
8
- { "id": "minimal", "name": "Minimal", "nologo": false,
9
- "description": "Typographic and unfilled - no logo, no colour. Prints cleanly in black and white." }
10
- ]
11
- }
@@ -1,83 +0,0 @@
1
- <div class="notes-plugin-wrap" style="padding: 24px; max-width: 1200px;">
2
-
3
- <!-- Toolbar -->
4
- <div class="notes-toolbar" style="display:flex;align-items:center;gap:12px;flex-wrap:wrap;margin-bottom:20px;">
5
- <h2 style="margin:0;flex:0 0 auto;font-size:1.4rem;">
6
- <span data-icon="file-text" data-icon-size="20" style="margin-right:8px;"></span>Notes
7
- </h2>
8
- <input
9
- id="notes-search"
10
- type="search"
11
- class="form-input"
12
- placeholder="Search notes…"
13
- style="flex:1;min-width:180px;max-width:320px;"
14
- />
15
- <button id="new-note-btn" class="btn btn-primary" style="flex-shrink:0;">
16
- <span data-icon="plus" data-icon-size="15" style="margin-right:6px;"></span>New Note
17
- </button>
18
- </div>
19
-
20
- <!-- Category filter -->
21
- <div id="category-filter" style="display:flex;flex-wrap:wrap;gap:6px;margin-bottom:18px;"></div>
22
-
23
- <!-- Notes table -->
24
- <div id="notes-table"></div>
25
-
26
- <!-- Editor overlay -->
27
- <div
28
- id="note-editor"
29
- style="display:none;position:fixed;inset:0;z-index:1000;background:rgba(0,0,0,0.45);
30
- align-items:center;justify-content:center;padding:20px;"
31
- >
32
- <div class="card" style="width:100%;max-width:640px;max-height:90vh;display:flex;flex-direction:column;">
33
- <div class="card-header" style="display:flex;align-items:center;justify-content:space-between;padding:16px 20px;">
34
- <strong style="font-size:1rem;">Edit Note</strong>
35
- <button id="cancel-note-btn" class="btn btn-sm btn-secondary" title="Close">
36
- <span data-icon="x" data-icon-size="15"></span>
37
- </button>
38
- </div>
39
- <div class="card-body" style="flex:1;overflow-y:auto;padding:20px;display:flex;flex-direction:column;gap:14px;">
40
- <div>
41
- <label for="note-title" class="form-label" style="font-weight:500;margin-bottom:4px;display:block;">Title</label>
42
- <input
43
- id="note-title"
44
- type="text"
45
- class="form-input"
46
- placeholder="Note title…"
47
- />
48
- </div>
49
- <div style="flex:1;display:flex;flex-direction:column;">
50
- <label for="note-content" class="form-label" style="font-weight:500;margin-bottom:4px;display:block;">Content</label>
51
- <textarea
52
- id="note-content"
53
- class="form-input"
54
- placeholder="Write your note here… (Ctrl+Enter to save)"
55
- style="flex:1;min-height:220px;resize:vertical;font-family:inherit;"
56
- ></textarea>
57
- </div>
58
- <div>
59
- <label for="note-categories" class="form-label" style="font-weight:500;margin-bottom:4px;display:block;">Categories</label>
60
- <input
61
- id="note-categories"
62
- type="text"
63
- class="form-input"
64
- placeholder="Comma-separated, e.g. work, ideas, personal"
65
- />
66
- <small style="opacity:0.6;margin-top:4px;display:block;">Separate multiple categories with commas.</small>
67
- </div>
68
- </div>
69
- <div class="card-footer" style="display:flex;align-items:center;justify-content:space-between;gap:10px;padding:14px 20px;">
70
- <button id="delete-note-btn" class="btn btn-danger btn-sm" style="display:none;">
71
- <span data-icon="trash" data-icon-size="13" style="margin-right:5px;"></span>Delete
72
- </button>
73
- <div style="display:flex;gap:8px;margin-left:auto;">
74
- <button id="cancel-note-btn-footer" class="btn btn-secondary btn-sm">Cancel</button>
75
- <button id="save-note-btn" class="btn btn-primary btn-sm">
76
- <span data-icon="save" data-icon-size="13" style="margin-right:5px;"></span>Save
77
- </button>
78
- </div>
79
- </div>
80
- </div>
81
- </div>
82
-
83
- </div>
@@ -1,281 +0,0 @@
1
- // ============================================================
2
- // Notes plugin admin view
3
- // Uses auth-aware api() helper for all API calls
4
- // Uses T.create() for table rendering (Domma Table component)
5
- // ============================================================
6
-
7
- const esc = (str) => String(str ?? '')
8
- .replace(/&/g, '&amp;')
9
- .replace(/</g, '&lt;')
10
- .replace(/>/g, '&gt;')
11
- .replace(/"/g, '&quot;')
12
- .replace(/'/g, '&#39;');
13
-
14
- function debounce(fn, delay) {
15
- let timer;
16
- return (...args) => {
17
- clearTimeout(timer);
18
- timer = setTimeout(() => fn(...args), delay);
19
- };
20
- }
21
-
22
- async function api(url, method = 'GET', body) {
23
- const opts = {method, headers: {'Authorization': 'Bearer ' + (S.get('auth_token') || '')}};
24
- if (body !== undefined) {
25
- opts.headers['Content-Type'] = 'application/json';
26
- opts.body = JSON.stringify(body);
27
- }
28
- const res = await fetch(url, opts);
29
- if (!res.ok) {
30
- const err = await res.json().catch(() => ({error: res.statusText}));
31
- throw new Error(err.error || res.statusText);
32
- }
33
- const text = await res.text();
34
- return text ? JSON.parse(text) : {};
35
- }
36
-
37
- export const notesView = {
38
- templateUrl: '/plugins/notes/admin/templates/notes.html',
39
-
40
- async onMount($container) {
41
- // ---- State ----
42
- let notes = [];
43
- let categories = [];
44
- let activeCategory = '';
45
- let searchQuery = '';
46
- let editingNoteId = null;
47
-
48
- // ---- DOM refs ----
49
- const editorEl = $container.find('#note-editor').get(0);
50
- const titleInput = $container.find('#note-title').get(0);
51
- const contentInput = $container.find('#note-content').get(0);
52
- const catsInput = $container.find('#note-categories').get(0);
53
- const deleteBtnEl = $container.find('#delete-note-btn').get(0);
54
- const catFilterEl = $container.find('#category-filter').get(0);
55
-
56
- // ---- Fetch ----
57
- async function loadNotes() {
58
- try {
59
- const params = new URLSearchParams();
60
- if (searchQuery) params.set('q', searchQuery);
61
- if (activeCategory) params.set('category', activeCategory);
62
- const qs = params.toString();
63
- const url = '/api/plugins/notes/items' + (qs ? '?' + qs : '');
64
- const res = await api(url);
65
- notes = Array.isArray(res) ? res : (res.data ?? []);
66
- } catch {
67
- E.toast('Failed to load notes', { type: 'error' });
68
- notes = [];
69
- }
70
- }
71
-
72
- async function loadCategories() {
73
- try {
74
- const res = await api('/api/plugins/notes/categories');
75
- categories = Array.isArray(res) ? res : [];
76
- } catch {
77
- categories = [];
78
- }
79
- }
80
-
81
- // ---- Render table ----
82
- function renderTable() {
83
- const wrap = $container.find('#notes-table').get(0);
84
- if (!wrap) return;
85
-
86
- T.create(wrap, {
87
- data: notes,
88
- columns: [
89
- {
90
- key: 'title',
91
- title: 'Title',
92
- sortable: true,
93
- render: (v) => `<strong>${esc(v || 'Untitled')}</strong>`
94
- },
95
- {
96
- key: 'content',
97
- title: 'Content',
98
- sortable: false,
99
- render: (v) => {
100
- const preview = (v || '').slice(0, 100);
101
- const ellipsis = v && v.length > 100 ? '\u2026' : '';
102
- return preview
103
- ? `<span style="font-size:0.87rem;opacity:0.8;">${esc(preview)}${ellipsis}</span>`
104
- : '<span style="opacity:0.35;">-</span>';
105
- }
106
- },
107
- {
108
- key: 'categories',
109
- title: 'Categories',
110
- sortable: false,
111
- render: (v) => {
112
- if (!Array.isArray(v) || v.length === 0) return '<span style="opacity:0.35;">-</span>';
113
- return v.map((c) =>
114
- `<span class="badge badge-secondary notes-cat-tag" data-cat="${esc(c)}" `
115
- + `style="font-size:0.7rem;margin-right:2px;cursor:pointer;">${esc(c)}</span>`
116
- ).join('');
117
- }
118
- },
119
- {
120
- key: 'updatedAt',
121
- title: 'Updated',
122
- sortable: true,
123
- render: (v) => v
124
- ? `<span title="${esc(D(v).format('DD MMM YYYY HH:mm'))}">${esc(D(v).fromNow())}</span>`
125
- : '-'
126
- },
127
- {
128
- key: 'id',
129
- title: 'Actions',
130
- sortable: false,
131
- render: (id) =>
132
- `<div style="display:flex;gap:.4rem;">` +
133
- `<button class="btn btn-sm btn-outline note-edit-btn" data-id="${esc(id)}">Edit</button>` +
134
- `<button class="btn btn-sm btn-danger note-delete-btn" data-id="${esc(id)}">Delete</button>` +
135
- `</div>`
136
- }
137
- ],
138
- emptyMessage: 'No notes yet. Click New Note to get started.'
139
- });
140
-
141
- wrap.querySelectorAll('.note-edit-btn').forEach((btn) => {
142
- btn.addEventListener('click', () => {
143
- const note = notes.find((n) => n.id === btn.dataset.id);
144
- if (note) openEditor(note);
145
- });
146
- });
147
-
148
- wrap.querySelectorAll('.note-delete-btn').forEach((btn) => {
149
- btn.addEventListener('click', () => deleteNote(btn.dataset.id));
150
- });
151
-
152
- wrap.querySelectorAll('.notes-cat-tag').forEach((tag) => {
153
- tag.addEventListener('click', () => {
154
- activeCategory = tag.dataset.cat;
155
- renderCategoryFilter();
156
- loadNotes().then(renderTable);
157
- });
158
- });
159
-
160
- Domma.icons.scan(wrap);
161
- }
162
-
163
- // ---- Render category filter bar (DOM methods - no innerHTML with user data) ----
164
- function renderCategoryFilter() {
165
- catFilterEl.textContent = '';
166
-
167
- const makeBtn = (label, cat) => {
168
- const btn = document.createElement('button');
169
- btn.className = 'btn btn-sm cat-filter-btn ' + (activeCategory === cat ? 'btn-primary' : 'btn-secondary');
170
- btn.dataset.cat = cat;
171
- btn.style.marginRight = '4px';
172
- btn.textContent = label;
173
- return btn;
174
- };
175
-
176
- catFilterEl.appendChild(makeBtn('All', ''));
177
- for (const c of categories) {
178
- catFilterEl.appendChild(makeBtn(c, c));
179
- }
180
- }
181
-
182
- // ---- Full reload ----
183
- async function reload() {
184
- await Promise.all([loadNotes(), loadCategories()]);
185
- renderTable();
186
- renderCategoryFilter();
187
- }
188
-
189
- // ---- Editor helpers ----
190
- function openEditor(note = null) {
191
- editingNoteId = note ? note.id : null;
192
- titleInput.value = note ? (note.title || '') : '';
193
- contentInput.value = note ? (note.content || '') : '';
194
- catsInput.value = note && Array.isArray(note.categories) ? note.categories.join(', ') : '';
195
- deleteBtnEl.style.display = note ? 'inline-flex' : 'none';
196
- editorEl.style.display = 'flex';
197
- titleInput.focus();
198
- }
199
-
200
- function closeEditor() {
201
- editorEl.style.display = 'none';
202
- editingNoteId = null;
203
- titleInput.value = '';
204
- contentInput.value = '';
205
- catsInput.value = '';
206
- }
207
-
208
- function parseCategoriesInput() {
209
- return catsInput.value.split(',').map((c) => c.trim()).filter(Boolean);
210
- }
211
-
212
- // ---- Save (create or update) ----
213
- async function saveNote() {
214
- const title = titleInput.value.trim();
215
- const content = contentInput.value.trim();
216
- const cats = parseCategoriesInput();
217
-
218
- try {
219
- if (editingNoteId) {
220
- await api('/api/plugins/notes/items/' + editingNoteId, 'PUT', {title, content, categories: cats});
221
- E.toast('Note updated', { type: 'success' });
222
- } else {
223
- await api('/api/plugins/notes/items', 'POST', {title, content, categories: cats});
224
- E.toast('Note created', { type: 'success' });
225
- }
226
- closeEditor();
227
- await reload();
228
- } catch {
229
- E.toast('Failed to save note', { type: 'error' });
230
- }
231
- }
232
-
233
- // ---- Delete ----
234
- async function deleteNote(id) {
235
- const confirmed = await E.confirm('Delete this note?');
236
- if (!confirmed) return;
237
-
238
- try {
239
- await api('/api/plugins/notes/items/' + id, 'DELETE');
240
- E.toast('Note deleted', { type: 'success' });
241
- if (editingNoteId === id) closeEditor();
242
- await reload();
243
- } catch {
244
- E.toast('Failed to delete note', { type: 'error' });
245
- }
246
- }
247
-
248
- // ---- Event bindings ----
249
-
250
- $container.find('#new-note-btn').get(0).addEventListener('click', () => openEditor());
251
- $container.find('#save-note-btn').get(0).addEventListener('click', saveNote);
252
- $container.find('#cancel-note-btn').get(0).addEventListener('click', closeEditor);
253
- $container.find('#cancel-note-btn-footer').get(0).addEventListener('click', closeEditor);
254
- deleteBtnEl.addEventListener('click', () => {
255
- if (editingNoteId) deleteNote(editingNoteId);
256
- });
257
-
258
- $container.find('#notes-search').get(0).addEventListener('input', debounce(async (e) => {
259
- searchQuery = e.target.value.trim();
260
- await loadNotes();
261
- renderTable();
262
- }, 300));
263
-
264
- catFilterEl.addEventListener('click', async (e) => {
265
- const btn = e.target.closest('.cat-filter-btn');
266
- if (!btn) return;
267
- activeCategory = btn.dataset.cat;
268
- renderCategoryFilter();
269
- await loadNotes();
270
- renderTable();
271
- });
272
-
273
- editorEl.addEventListener('keydown', (e) => {
274
- if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') saveNote();
275
- });
276
-
277
- // ---- Initial load ----
278
- await reload();
279
- Domma.icons.scan($container.get(0));
280
- }
281
- };
@@ -1,54 +0,0 @@
1
- {
2
- "slug": "notes-notes",
3
- "title": "Notes",
4
- "description": "Notes managed by the Notes plugin.",
5
- "plugin": "notes",
6
- "fields": [
7
- {
8
- "name": "title",
9
- "label": "Title",
10
- "type": "text",
11
- "required": false
12
- },
13
- {
14
- "name": "content",
15
- "label": "Content",
16
- "type": "textarea",
17
- "required": false
18
- },
19
- {
20
- "name": "categories",
21
- "label": "Categories",
22
- "type": "text",
23
- "required": false
24
- },
25
- {
26
- "name": "userId",
27
- "label": "User ID",
28
- "type": "text",
29
- "required": false
30
- }
31
- ],
32
- "api": {
33
- "create": {
34
- "enabled": true,
35
- "access": "admin"
36
- },
37
- "read": {
38
- "enabled": true,
39
- "access": "admin"
40
- },
41
- "update": {
42
- "enabled": false,
43
- "access": "admin"
44
- },
45
- "delete": {
46
- "enabled": false,
47
- "access": "admin"
48
- }
49
- },
50
- "storage": {
51
- "adapter": "mongodb",
52
- "connection": "default"
53
- }
54
- }
@@ -1,6 +0,0 @@
1
- export default {
2
- scope: 'user',
3
- maxPerUser: 1000,
4
- // storage.adapter: 'mongodb' uses config/connections.json; 'file' uses local JSON
5
- storage: {adapter: 'mongodb', connection: 'default'}
6
- };