domma-cms 0.55.1 → 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 (305) hide show
  1. package/CLAUDE.md +194 -3
  2. package/README.md +18 -14
  3. package/admin/css/admin.css +1 -1
  4. package/admin/css/contacts.css +1 -0
  5. package/admin/dist/domma/domma-tools.css +3 -3
  6. package/admin/dist/domma/domma-tools.min.js +3 -3
  7. package/admin/index.html +1 -0
  8. package/admin/js/api.js +1 -1
  9. package/admin/js/app.js +4 -4
  10. package/admin/js/lib/analytics-shape.js +1 -0
  11. package/admin/js/lib/contacts-arrange.js +1 -0
  12. package/admin/js/lib/help-popover.js +1 -0
  13. package/admin/js/lib/notes-arrange.js +1 -0
  14. package/admin/js/lib/page-picker.js +1 -0
  15. package/admin/js/lib/plugin-accent.js +1 -0
  16. package/admin/js/lib/plugin-chrome.js +1 -0
  17. package/admin/js/lib/shortcode-context-menu.js +2 -2
  18. package/admin/js/lib/sidebar-grouping.js +1 -1
  19. package/admin/js/lib/sidebar-grouping.test.js +1 -1
  20. package/admin/js/lib/sidebar-renderer.js +4 -4
  21. package/admin/js/lib/slideover-resizable.js +1 -0
  22. package/admin/js/lib/todo-arrange.js +1 -0
  23. package/admin/js/lib/tool-kit.js +1 -0
  24. package/admin/js/templates/analytics.html +138 -0
  25. package/admin/js/templates/contacts.html +156 -0
  26. package/admin/js/templates/context-menu-editor.html +212 -0
  27. package/admin/js/templates/context-menus.html +16 -0
  28. package/admin/js/templates/notes.html +137 -0
  29. package/admin/js/templates/plugin-code.html +1 -1
  30. package/admin/js/templates/plugin-marketplace.html +17 -4
  31. package/admin/js/templates/plugins.html +13 -7
  32. package/{plugins/site-search/admin/templates/site-search.html → admin/js/templates/search.html} +50 -4
  33. package/admin/js/templates/settings.html +16 -97
  34. package/admin/js/templates/theme.html +211 -0
  35. package/admin/js/templates/todo.html +113 -0
  36. package/admin/js/views/analytics.js +62 -0
  37. package/admin/js/views/contacts.js +174 -0
  38. package/admin/js/views/context-menu-editor.js +55 -0
  39. package/admin/js/views/context-menus.js +5 -0
  40. package/admin/js/views/form-editor.js +7 -7
  41. package/admin/js/views/index.js +1 -1
  42. package/admin/js/views/notes.js +104 -0
  43. package/admin/js/views/plugin-marketplace.js +1 -1
  44. package/admin/js/views/plugins.js +28 -24
  45. package/admin/js/views/search.js +1 -0
  46. package/admin/js/views/settings.js +3 -3
  47. package/admin/js/views/theme.js +1 -0
  48. package/admin/js/views/todo.js +96 -0
  49. package/bin/cli.js +6 -10
  50. package/bin/lib/plugin-version.js +28 -0
  51. package/bin/update.js +27 -2
  52. package/config/menus/admin-sidebar.json +129 -23
  53. package/config/plugins.json +5 -5
  54. package/config/search.json +13 -0
  55. package/config/server.json +3 -1
  56. package/package.json +14 -10
  57. package/plugins/_lib/admin/mail/compose-window.js +914 -0
  58. package/plugins/_lib/admin/mail/contacts.js +301 -0
  59. package/plugins/_lib/admin/mail/diagnostics-section.js +133 -0
  60. package/plugins/_lib/admin/mail/folder-tree.js +254 -0
  61. package/plugins/_lib/admin/mail/identity.js +480 -0
  62. package/plugins/_lib/admin/mail/image-senders-section.js +131 -0
  63. package/plugins/_lib/admin/mail/keyboard.js +136 -0
  64. package/plugins/_lib/admin/mail/mail.css +1 -0
  65. package/plugins/_lib/admin/mail/mail.html +71 -0
  66. package/plugins/_lib/admin/mail/panes.js +253 -0
  67. package/plugins/_lib/admin/mail/reader-view.js +4473 -0
  68. package/plugins/_lib/admin/mail/resizable.js +26 -0
  69. package/plugins/_lib/admin/mail/rules.js +343 -0
  70. package/plugins/_lib/admin/mail/scheduling.js +203 -0
  71. package/plugins/_lib/admin/mail/section-kit.js +277 -0
  72. package/plugins/_lib/admin/mail/templates.js +238 -0
  73. package/plugins/_lib/admin/mail/threads.js +200 -0
  74. package/plugins/_lib/admin/mail/vacation.js +269 -0
  75. package/plugins/_lib/admin/ui/help.css +1 -0
  76. package/plugins/_lib/admin/ui/help.js +174 -0
  77. package/plugins/_lib/admin/ui/resizable.js +151 -0
  78. package/plugins/_lib/dataStore.js +117 -0
  79. package/plugins/_lib/mail/accounts.js +919 -0
  80. package/plugins/_lib/mail/bodyTokens.js +101 -0
  81. package/plugins/_lib/mail/compose.js +256 -0
  82. package/plugins/_lib/mail/defaults.js +57 -0
  83. package/plugins/_lib/mail/diagnostics.js +154 -0
  84. package/plugins/_lib/mail/envelope.js +161 -0
  85. package/plugins/_lib/mail/folders.js +192 -0
  86. package/plugins/_lib/mail/handoff.js +274 -0
  87. package/plugins/_lib/mail/imapPool.js +293 -0
  88. package/plugins/_lib/mail/mbox.js +74 -0
  89. package/plugins/_lib/mail/pollSchedule.js +79 -0
  90. package/plugins/_lib/mail/poller.js +135 -0
  91. package/plugins/_lib/mail/priority.js +138 -0
  92. package/plugins/_lib/mail/readRoutes.js +680 -0
  93. package/plugins/_lib/mail/render.js +291 -0
  94. package/plugins/_lib/mail/ruleRunner.js +152 -0
  95. package/plugins/_lib/mail/scheduler.js +254 -0
  96. package/plugins/_lib/mail/secretbox.js +229 -0
  97. package/plugins/_lib/mail/send.js +396 -0
  98. package/plugins/_lib/mail/store.js +1022 -0
  99. package/plugins/_lib/mail/sync.js +292 -0
  100. package/plugins/_lib/mail/syncPlan.js +126 -0
  101. package/plugins/_lib/mail/syncSelection.js +82 -0
  102. package/plugins/_lib/mail/unsubscribe.js +183 -0
  103. package/plugins/_lib/mail/vacationRunner.js +114 -0
  104. package/plugins/_lib/mail/write.js +473 -0
  105. package/plugins/_lib/schemaSync.js +83 -0
  106. package/plugins/_template/admin/css/index.css +0 -0
  107. package/plugins/_template/admin/templates/index.html +4 -4
  108. package/plugins/_template/admin/views/index.js +7 -0
  109. package/plugins/blog/CLAUDE.md +237 -0
  110. package/plugins/blog/admin/css/index.css +178 -0
  111. package/plugins/blog/admin/templates/blog.html +35 -41
  112. package/plugins/blog/admin/templates/categories.html +19 -6
  113. package/plugins/blog/admin/templates/comments.html +26 -9
  114. package/plugins/blog/admin/templates/post-editor.html +63 -40
  115. package/plugins/blog/admin/templates/settings.html +3 -9
  116. package/plugins/blog/admin/views/blog.js +288 -159
  117. package/plugins/blog/admin/views/categories.js +212 -206
  118. package/plugins/blog/admin/views/comments.js +207 -158
  119. package/plugins/blog/admin/views/kit.js +372 -0
  120. package/plugins/blog/admin/views/post-editor.js +306 -39
  121. package/plugins/blog/admin/views/settings-cog.js +56 -0
  122. package/plugins/blog/admin/views/settings.js +16 -84
  123. package/plugins/blog/blocks/blog-card-compact.css +17 -0
  124. package/plugins/blog/blocks/blog-card-compact.html +8 -0
  125. package/plugins/blog/blocks/blog-card-row.css +22 -0
  126. package/plugins/blog/blocks/blog-card-row.html +11 -0
  127. package/plugins/blog/blocks/blog-card.css +35 -0
  128. package/plugins/blog/blocks/blog-card.html +16 -0
  129. package/plugins/blog/blocks/blog-post-classic.css +15 -0
  130. package/plugins/blog/blocks/blog-post-classic.html +28 -0
  131. package/plugins/blog/blocks/blog-post-feature.css +20 -0
  132. package/plugins/blog/blocks/blog-post-feature.html +30 -0
  133. package/plugins/blog/blocks/blog-post-minimal.css +10 -0
  134. package/plugins/blog/blocks/blog-post-minimal.html +23 -0
  135. package/plugins/blog/blocks/blog-post-sidebar.css +22 -0
  136. package/plugins/blog/blocks/blog-post-sidebar.html +36 -0
  137. package/plugins/blog/collections/categories/schema.json +7 -6
  138. package/plugins/blog/collections/comments/schema.json +11 -10
  139. package/plugins/blog/collections/posts/schema.json +16 -13
  140. package/plugins/blog/config.js +6 -0
  141. package/plugins/blog/lib/layouts.js +304 -0
  142. package/plugins/blog/lib/page.js +158 -0
  143. package/plugins/blog/lib/render.js +119 -0
  144. package/plugins/blog/lib/samples.js +234 -0
  145. package/plugins/blog/plugin.js +259 -26
  146. package/plugins/blog/plugin.json +21 -73
  147. package/plugins/blog/plugin.public.js +217 -0
  148. package/plugins/blog/public/blog.css +177 -0
  149. package/plugins/blog/public/blog.js +586 -0
  150. package/plugins/blog/public/samples/aurora.svg +7 -0
  151. package/plugins/blog/public/samples/dusk.svg +7 -0
  152. package/plugins/blog/public/samples/ember.svg +7 -0
  153. package/plugins/blog/public/samples/harbour.svg +7 -0
  154. package/plugins/blog/public/samples/meadow.svg +7 -0
  155. package/plugins/blog/public/samples/rosewood.svg +7 -0
  156. package/plugins/blog/templates/index.html +9 -8
  157. package/plugins/blog/templates/post.html +4 -17
  158. package/plugins/blog/tests/layouts.test.js +97 -0
  159. package/plugins/blog/tests/public.test.js +71 -0
  160. package/plugins/free-tier.lock.json +74 -0
  161. package/plugins/mail-reader/CLAUDE.md +78 -0
  162. package/plugins/mail-reader/admin/views/mail.js +19 -0
  163. package/plugins/mail-reader/config.js +7 -0
  164. package/plugins/mail-reader/plugin.js +48 -0
  165. package/plugins/mail-reader/plugin.json +34 -0
  166. package/public/css/forms.css +1 -1
  167. package/public/css/search.css +1 -0
  168. package/public/css/site.css +1 -1
  169. package/public/css/theme-switcher.css +1 -0
  170. package/public/js/analytics.js +1 -0
  171. package/public/js/collection-context.js +2 -2
  172. package/public/js/context-menus.js +1 -0
  173. package/public/js/form-logic-engine.js +1 -1
  174. package/public/js/forms.js +2 -2
  175. package/public/js/search.js +1 -0
  176. package/public/js/site.js +1 -1
  177. package/public/js/theme-switcher.js +1 -0
  178. package/scripts/build.js +77 -4
  179. package/scripts/copy-domma.js +48 -0
  180. package/scripts/run-tests.mjs +92 -0
  181. package/scripts/seed.js +1996 -0
  182. package/{plugins/analytics/plugin.js → server/routes/api/analytics.js} +89 -41
  183. package/server/routes/api/collections.js +34 -0
  184. package/server/routes/api/contacts.js +506 -0
  185. package/server/routes/api/context-menus.js +104 -0
  186. package/server/routes/api/dashboard.js +8 -2
  187. package/server/routes/api/forms.js +42 -3
  188. package/{plugins/notes/plugin.js → server/routes/api/notes.js} +61 -17
  189. package/server/routes/api/notifications.js +69 -19
  190. package/server/routes/api/plugin-marketplace.js +78 -23
  191. package/server/routes/api/plugins.js +198 -9
  192. package/server/routes/api/search.js +43 -0
  193. package/server/routes/api/theme.js +69 -0
  194. package/server/routes/api/todo.js +178 -0
  195. package/server/routes/public.js +42 -7
  196. package/server/server.js +135 -1
  197. package/server/services/adapters/FileAdapter.js +6 -1
  198. package/server/services/collections.js +13 -3
  199. package/server/services/content.js +26 -0
  200. package/server/services/contextMenus.js +477 -0
  201. package/server/services/managerClient.js +72 -136
  202. package/server/services/markdown.js +124 -10
  203. package/server/services/permissionRegistry.js +74 -0
  204. package/server/services/pluginEntitlement.js +171 -0
  205. package/server/services/pluginEnvelope.js +242 -0
  206. package/server/services/pluginFiles.js +52 -11
  207. package/server/services/pluginInstaller.js +246 -26
  208. package/server/services/pluginScaffold.js +5 -0
  209. package/server/services/plugins.js +340 -7
  210. package/server/services/presetCollections.js +344 -0
  211. package/server/services/promoted-plugins-migration.js +254 -0
  212. package/server/services/publicCors.js +46 -0
  213. package/server/services/renderer.js +224 -22
  214. package/server/services/roles.js +1 -1
  215. package/server/services/search-migration.js +82 -0
  216. package/server/services/search.js +413 -0
  217. package/server/services/sidebar-migration.js +1 -0
  218. package/server/services/sidebarBadges.js +37 -0
  219. package/server/services/siteGitignore.js +307 -0
  220. package/server/services/themeSettings.js +580 -0
  221. package/server/services/users.js +8 -0
  222. package/server/templates/page.html +4 -2
  223. package/plugins/analytics/admin/templates/analytics.html +0 -61
  224. package/plugins/analytics/admin/views/analytics.js +0 -176
  225. package/plugins/analytics/config.js +0 -14
  226. package/plugins/analytics/plugin.json +0 -57
  227. package/plugins/analytics/public/inject-body.html +0 -60
  228. package/plugins/analytics/public/inject-head.html +0 -1
  229. package/plugins/blog/templates/author.html +0 -9
  230. package/plugins/blog/templates/category.html +0 -9
  231. package/plugins/blog/templates/tag.html +0 -9
  232. package/plugins/contacts/admin/templates/contacts.html +0 -126
  233. package/plugins/contacts/admin/views/contacts.js +0 -710
  234. package/plugins/contacts/collections/user-contact-groups/schema.json +0 -35
  235. package/plugins/contacts/collections/user-contacts/schema.json +0 -71
  236. package/plugins/contacts/config.js +0 -6
  237. package/plugins/contacts/data/contacts.json +0 -20
  238. package/plugins/contacts/plugin.js +0 -291
  239. package/plugins/contacts/plugin.json +0 -33
  240. package/plugins/demo-viewer/config.js +0 -4
  241. package/plugins/demo-viewer/plugin.js +0 -36
  242. package/plugins/demo-viewer/plugin.json +0 -9
  243. package/plugins/invoice/admin/templates/editor.html +0 -129
  244. package/plugins/invoice/admin/templates/index.html +0 -43
  245. package/plugins/invoice/admin/templates/issuers.html +0 -5
  246. package/plugins/invoice/admin/templates/receivers.html +0 -5
  247. package/plugins/invoice/admin/views/editor.js +0 -267
  248. package/plugins/invoice/admin/views/index.js +0 -155
  249. package/plugins/invoice/admin/views/issuers.js +0 -23
  250. package/plugins/invoice/admin/views/party-view.js +0 -148
  251. package/plugins/invoice/admin/views/receivers.js +0 -22
  252. package/plugins/invoice/collections/invoice-issuers/schema.json +0 -16
  253. package/plugins/invoice/collections/invoice-receivers/schema.json +0 -15
  254. package/plugins/invoice/collections/invoices/schema.json +0 -27
  255. package/plugins/invoice/config.js +0 -16
  256. package/plugins/invoice/plugin.js +0 -283
  257. package/plugins/invoice/plugin.json +0 -85
  258. package/plugins/invoice/templates/invoice-print.html +0 -213
  259. package/plugins/notes/admin/templates/notes.html +0 -83
  260. package/plugins/notes/admin/views/notes.js +0 -281
  261. package/plugins/notes/collections/user-notes/schema.json +0 -54
  262. package/plugins/notes/config.js +0 -6
  263. package/plugins/notes/data/notes.json +0 -1
  264. package/plugins/notes/plugin.json +0 -33
  265. package/plugins/site-search/admin/views/site-search.js +0 -116
  266. package/plugins/site-search/config.js +0 -15
  267. package/plugins/site-search/plugin.js +0 -188
  268. package/plugins/site-search/plugin.json +0 -40
  269. package/plugins/site-search/public/inject-body.html +0 -17
  270. package/plugins/site-search/public/inject-head.html +0 -1
  271. package/plugins/site-search/public/search.css +0 -1
  272. package/plugins/site-search/public/search.js +0 -1
  273. package/plugins/surveys/admin/templates/audience.html +0 -47
  274. package/plugins/surveys/admin/templates/results.html +0 -58
  275. package/plugins/surveys/admin/templates/survey-editor.html +0 -141
  276. package/plugins/surveys/admin/templates/surveys.html +0 -25
  277. package/plugins/surveys/admin/views/audience.js +0 -301
  278. package/plugins/surveys/admin/views/results.js +0 -172
  279. package/plugins/surveys/admin/views/survey-editor.js +0 -211
  280. package/plugins/surveys/admin/views/surveys.js +0 -161
  281. package/plugins/surveys/collections/survey-contacts/schema.json +0 -13
  282. package/plugins/surveys/collections/survey-groups/schema.json +0 -11
  283. package/plugins/surveys/collections/survey-invites/schema.json +0 -16
  284. package/plugins/surveys/collections/surveys/schema.json +0 -23
  285. package/plugins/surveys/config.js +0 -8
  286. package/plugins/surveys/plugin.js +0 -174
  287. package/plugins/surveys/plugin.json +0 -65
  288. package/plugins/surveys/public/dist-shared.mjs +0 -1
  289. package/plugins/surveys/public/survey.css +0 -1
  290. package/plugins/surveys/public/survey.mjs +0 -1
  291. package/plugins/surveys/templates/survey-page.html +0 -63
  292. package/plugins/theme-switcher/admin/templates/theme-switcher.html +0 -86
  293. package/plugins/theme-switcher/admin/views/theme-switcher.js +0 -65
  294. package/plugins/theme-switcher/config.js +0 -10
  295. package/plugins/theme-switcher/plugin.js +0 -26
  296. package/plugins/theme-switcher/plugin.json +0 -74
  297. package/plugins/theme-switcher/public/inject-body.html +0 -153
  298. package/plugins/theme-switcher/public/inject-head.html +0 -260
  299. package/plugins/todo/admin/templates/todo.html +0 -158
  300. package/plugins/todo/admin/views/todo.js +0 -343
  301. package/plugins/todo/collections/todos/schema.json +0 -60
  302. package/plugins/todo/config.js +0 -7
  303. package/plugins/todo/data/todos.json +0 -1
  304. package/plugins/todo/plugin.js +0 -102
  305. package/plugins/todo/plugin.json +0 -33
@@ -1,188 +0,0 @@
1
- /**
2
- * Site Search Plugin - Server
3
- *
4
- * Endpoints (prefix: /api/plugins/site-search):
5
- * GET /search?q=term - public: full-text search across all published pages
6
- * GET /settings - public: return plugin settings (used by inject script)
7
- * PUT /settings - admin-auth: save user overrides
8
- */
9
- import {listPages} from '../../server/services/content.js';
10
- import {getPluginSettings, savePluginState} from '../../server/services/plugins.js';
11
-
12
- /**
13
- * Strip markdown syntax and shortcodes from content for plain-text searching.
14
- *
15
- * @param {string} content
16
- * @returns {string}
17
- */
18
- function stripMarkdown(content) {
19
- if (!content) return '';
20
- return content
21
- // Remove shortcodes (self-closing and wrapping)
22
- .replace(/\[[a-z][^\]]*\/\]/gi, '')
23
- .replace(/\[[a-z][^\]]*\][\s\S]*?\[\/[a-z]+\]/gi, '')
24
- .replace(/\[[a-z][^\]]*\]/gi, '')
25
- // Remove HTML tags
26
- .replace(/<[^>]+>/g, '')
27
- // Remove headings (keep text)
28
- .replace(/^#{1,6}\s+/gm, '')
29
- // Remove bold / italic
30
- .replace(/\*{1,3}([^*]+)\*{1,3}/g, '$1')
31
- .replace(/_{1,3}([^_]+)_{1,3}/g, '$1')
32
- // Remove images
33
- .replace(/!\[[^\]]*\]\([^)]*\)/g, '')
34
- // Remove links (keep text)
35
- .replace(/\[([^\]]+)\]\([^)]*\)/g, '$1')
36
- // Remove code blocks
37
- .replace(/```[\s\S]*?```/g, '')
38
- .replace(/`[^`]+`/g, '')
39
- // Remove blockquotes marker
40
- .replace(/^>\s+/gm, '')
41
- // Collapse whitespace
42
- .replace(/\s+/g, ' ')
43
- .trim();
44
- }
45
-
46
- /**
47
- * Build a snippet of ~length chars around the first occurrence of term in content.
48
- *
49
- * @param {string} rawContent - raw markdown content
50
- * @param {string} term
51
- * @param {number} length
52
- * @returns {string}
53
- */
54
- function buildSnippet(rawContent, term, length = 120) {
55
- const text = stripMarkdown(rawContent);
56
- if (!text) return '';
57
-
58
- const lower = text.toLowerCase();
59
- const idx = lower.indexOf(term.toLowerCase());
60
-
61
- if (idx === -1) {
62
- return text.length > length ? text.slice(0, length) + '…' : text;
63
- }
64
-
65
- const half = Math.floor(length / 2);
66
- const start = Math.max(0, idx - half);
67
- const end = Math.min(text.length, start + length);
68
- const snippet = text.slice(start, end);
69
-
70
- return (start > 0 ? '…' : '') + snippet + (end < text.length ? '…' : '');
71
- }
72
-
73
- /**
74
- * Score a page against a search query using weighted field matching.
75
- *
76
- * @param {object} page
77
- * @param {string} query
78
- * @param {string[]} words - query split into words
79
- * @returns {number}
80
- */
81
- function scorePage(page, query, words) {
82
- let score = 0;
83
- const title = (page.title || '').toLowerCase();
84
- const description = (page.description || '').toLowerCase();
85
- const tagsRaw = Array.isArray(page.tags) ? page.tags : [];
86
- const tags = tagsRaw.map(t => t.toLowerCase());
87
- const stripped = stripMarkdown(page.content || '').toLowerCase();
88
- const q = query.toLowerCase();
89
-
90
- // Title scoring
91
- if (title === q) {
92
- score += 100;
93
- } else if (title.includes(q)) {
94
- score += 60;
95
- } else if (words.every(w => title.includes(w))) {
96
- score += 40;
97
- } else if (words.some(w => title.includes(w))) {
98
- score += 20;
99
- }
100
-
101
- // Tags scoring
102
- if (tags.includes(q)) {
103
- score += 30;
104
- } else if (tags.some(t => t.includes(q))) {
105
- score += 20;
106
- } else if (words.every(w => tags.some(t => t.includes(w)))) {
107
- score += 15;
108
- } else if (words.some(w => tags.some(t => t.includes(w)))) {
109
- score += 8;
110
- }
111
-
112
- // Description scoring
113
- if (description.includes(q)) {
114
- score += 25;
115
- } else if (words.some(w => description.includes(w))) {
116
- score += 10;
117
- }
118
-
119
- // Content scoring
120
- if (stripped.includes(q)) {
121
- score += 10;
122
- } else if (words.every(w => stripped.includes(w))) {
123
- score += 5;
124
- } else if (words.some(w => stripped.includes(w))) {
125
- score += 2;
126
- }
127
-
128
- return score;
129
- }
130
-
131
- export default async function siteSearchPlugin(fastify, options) {
132
- const {authenticate, requireAdmin} = options.auth;
133
-
134
- // -------------------------------------------------------------------------
135
- // Public search endpoint
136
- // -------------------------------------------------------------------------
137
-
138
- fastify.get('/search', async (request, reply) => {
139
- const settings = getPluginSettings('site-search');
140
- const minLen = settings.minQueryLength ?? 2;
141
- const maxResults = settings.maxResults ?? 10;
142
-
143
- const q = (request.query.q || '').trim();
144
- if (q.length < minLen) {
145
- return reply.status(400).send({error: `Query must be at least ${minLen} characters`});
146
- }
147
-
148
- const pages = await listPages();
149
- const words = q.toLowerCase().split(/\s+/).filter(Boolean);
150
-
151
- const results = [];
152
- for (const page of pages) {
153
- // Filter out drafts and private pages
154
- if (page.status === 'draft') continue;
155
- if (page.visibility === 'private') continue;
156
-
157
- const score = scorePage(page, q, words);
158
- if (score === 0) continue;
159
-
160
- results.push({
161
- title: page.title || 'Untitled',
162
- url: page.urlPath,
163
- description: page.description || '',
164
- tags: Array.isArray(page.tags) ? page.tags : [],
165
- snippet: buildSnippet(page.content || '', q),
166
- score
167
- });
168
- }
169
-
170
- results.sort((a, b) => b.score - a.score);
171
-
172
- return {results: results.slice(0, maxResults)};
173
- });
174
-
175
- // -------------------------------------------------------------------------
176
- // Settings routes
177
- // -------------------------------------------------------------------------
178
-
179
- fastify.get('/settings', async () => {
180
- return getPluginSettings('site-search');
181
- });
182
-
183
- fastify.put('/settings', {preHandler: [authenticate, requireAdmin]}, async (request) => {
184
- const body = request.body || {};
185
- savePluginState('site-search', {settings: body});
186
- return {ok: true};
187
- });
188
- }
@@ -1,40 +0,0 @@
1
- {
2
- "name": "site-search",
3
- "displayName": "Site Search",
4
- "version": "1.0.0",
5
- "description": "Full-text search for the public site. Search icon in navbar, Cmd+K shortcut, weighted results.",
6
- "author": "Darryl Waterhouse",
7
- "date": "2026-03-19",
8
- "icon": "search",
9
- "admin": {
10
- "sidebar": [
11
- {
12
- "id": "site-search",
13
- "text": "Site Search",
14
- "icon": "search",
15
- "url": "#/plugins/site-search",
16
- "section": "#/plugins/site-search",
17
- "permissions": [
18
- "settings"
19
- ]
20
- }
21
- ],
22
- "routes": [
23
- {
24
- "path": "/plugins/site-search",
25
- "view": "plugin-site-search",
26
- "title": "Site Search - Domma CMS"
27
- }
28
- ],
29
- "views": {
30
- "plugin-site-search": {
31
- "entry": "site-search/admin/views/site-search.js?v=0c7dd970",
32
- "exportName": "siteSearchView"
33
- }
34
- }
35
- },
36
- "inject": {
37
- "headLate": "public/inject-head.html",
38
- "bodyEnd": "public/inject-body.html"
39
- }
40
- }
@@ -1,17 +0,0 @@
1
- <!-- site-search: load settings then initialise search UI -->
2
- <script>
3
- (function () {
4
- fetch('/api/plugins/site-search/settings')
5
- .then(function (r) {
6
- return r.json();
7
- })
8
- .then(function (cfg) {
9
- window.__SITE_SEARCH__ = cfg;
10
- var s = document.createElement('script');
11
- s.src = '/plugins/site-search/public/search.js';
12
- document.body.appendChild(s);
13
- })
14
- .catch(function () {
15
- });
16
- }());
17
- </script>
@@ -1 +0,0 @@
1
- <link rel="stylesheet" href="/plugins/site-search/public/search.css?v=2">
@@ -1 +0,0 @@
1
- .site-search-trigger{display:inline-flex;align-items:center;justify-content:center;background:none;border:none;cursor:pointer;padding:6px 8px;border-radius:var(--dm-radius, 6px);color:inherit;opacity:.75;transition:opacity .15s,background .15s;font-size:var(--dm-font-size-sm, 14px)}.site-search-trigger:hover{opacity:1;background:var(--dm-surface-hover, rgba(0, 0, 0, .06))}.site-search-trigger svg,.site-search-trigger span[data-icon]{width:18px;height:18px}.site-search-shortcut-hint{display:inline-block;font-size:11px;line-height:1;padding:2px 5px;border-radius:4px;border:1px solid var(--dm-border, rgba(0, 0, 0, .15));color:var(--dm-text-muted, #888);margin-left:4px;vertical-align:middle;font-family:var(--dm-font-mono, monospace)}.site-search-overlay{position:fixed;inset:0;z-index:10000;background:#00000073;backdrop-filter:blur(3px);-webkit-backdrop-filter:blur(3px);display:flex;align-items:flex-start;justify-content:center;padding-top:10vh;animation:ss-overlay-in .15s ease}@keyframes ss-overlay-in{0%{opacity:0}to{opacity:1}}.site-search-panel{width:100%;max-width:600px;margin:0 16px;border-radius:var(--dm-radius-lg, 10px);background:#1e2030;color:#e8eaf0;border:1px solid rgba(255,255,255,.1);box-shadow:0 24px 64px #0009;overflow:hidden;animation:ss-panel-in .15s ease}@keyframes ss-panel-in{0%{transform:translateY(-12px);opacity:0}to{transform:translateY(0);opacity:1}}.site-search-header{display:flex;align-items:center;padding:12px 16px;border-bottom:1px solid rgba(255,255,255,.08);gap:8px}.site-search-header span[data-icon],.site-search-header svg{width:18px;height:18px;flex-shrink:0;opacity:.45;color:#e8eaf0}.site-search-input{flex:1;border:none;outline:none;background:transparent;font-size:var(--dm-font-size-lg, 16px);color:#e8eaf0;font-family:var(--dm-font-sans, sans-serif);padding:0}.site-search-input::placeholder{color:#e8eaf066;opacity:1}.site-search-close-btn{display:inline-flex;align-items:center;justify-content:center;background:none;border:1px solid rgba(255,255,255,.15);cursor:pointer;color:#e8eaf080;border-radius:var(--dm-radius, 6px);padding:4px 8px;font-size:12px;font-family:var(--dm-font-mono, monospace);transition:color .15s,border-color .15s;flex-shrink:0}.site-search-close-btn:hover{color:#e8eaf0;border-color:#ffffff4d}.site-search-results{max-height:60vh;overflow-y:auto;padding:8px 0}.site-search-result{display:block;padding:10px 16px;text-decoration:none;color:inherit;border-left:3px solid transparent;transition:background .1s,border-color .1s;cursor:pointer}.site-search-result:hover,.site-search-result.active{background:#ffffff0f;border-left-color:var(--dm-primary, #5b8cff)}.site-search-result-title{font-weight:600;font-size:var(--dm-font-size-sm, 14px);margin-bottom:2px;color:#e8eaf0}.site-search-result-snippet{font-size:12px;color:#e8eaf08c;line-height:1.4;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.site-search-result-snippet mark{background:#5b8cff40;color:#e8eaf0;border-radius:2px;padding:0 1px}.site-search-loading,.site-search-empty{padding:24px 16px;text-align:center;color:#e8eaf073;font-size:var(--dm-font-size-sm, 14px)}.site-search-loading-dots{display:inline-flex;gap:4px}.site-search-loading-dots span{width:6px;height:6px;border-radius:50%;background:#e8eaf066;animation:ss-dot-pulse 1.2s ease-in-out infinite}.site-search-loading-dots span:nth-child(2){animation-delay:.2s}.site-search-loading-dots span:nth-child(3){animation-delay:.4s}@keyframes ss-dot-pulse{0%,80%,to{transform:scale(.6);opacity:.4}40%{transform:scale(1);opacity:1}}.site-search-results:not(:empty){border-top:1px solid rgba(255,255,255,.06)}.site-search-footer{padding:6px 16px;border-top:1px solid rgba(255,255,255,.06);display:flex;gap:12px;font-size:11px;color:#e8eaf059}.site-search-footer kbd{display:inline-block;padding:1px 4px;border-radius:3px;border:1px solid rgba(255,255,255,.15);font-family:var(--dm-font-mono, monospace);font-size:10px}@media(max-width:640px){.site-search-overlay{padding-top:5vh;align-items:flex-start}.site-search-panel{margin:0 8px;border-radius:var(--dm-radius, 6px)}.site-search-results{max-height:70vh}.site-search-shortcut-hint{display:none}}.site-search-floating-trigger{position:fixed;z-index:9999;width:52px;height:52px;border-radius:50%;border:1px solid var(--dm-border, rgba(0, 0, 0, .12));cursor:pointer;display:flex;align-items:center;justify-content:center;gap:8px;background:var(--dm-surface, #fff);color:var(--dm-text, #111);box-shadow:0 4px 16px #00000024;transition:background .15s,color .15s,border-color .15s,transform .15s,box-shadow .15s;outline:none;white-space:nowrap}.site-search-floating-trigger:hover{background:var(--dm-primary, #5b8cff);color:#fff;border-color:var(--dm-primary, #5b8cff);box-shadow:0 6px 20px #00000038;transform:scale(1.05)}.site-search-floating-trigger:active{transform:scale(.97)}.site-search-floating-trigger svg,.site-search-floating-trigger [data-icon]{width:20px;height:20px;flex-shrink:0;pointer-events:none}.site-search-floating-trigger--pill{width:auto;height:44px;border-radius:999px;padding:0 18px 0 14px}.site-search-floating-label{font-size:.875rem;font-weight:500;letter-spacing:.01em;pointer-events:none}
@@ -1 +0,0 @@
1
- (function(){"use strict";var l=window.__SITE_SEARCH__||{},N=l.placeholder||"Search pages...",g=l.keyboardShortcut!==!1,w=l.debounceMs||300,D=l.displayMode||"navbar",y=l.iconOnly!==!1,u=null,s=null,c=null,p=null,f=!1;function h(){var t=document.querySelector("#site-navbar, .navbar");if(t){var e=t.querySelector(".navbar-actions");if(!e){var n=t.querySelector(".navbar-container")||t;e=document.createElement("div"),e.className="navbar-actions",n.appendChild(e)}if(!e.querySelector(".site-search-trigger")){var r=/Mac|iPhone|iPad|iPod/.test(navigator.platform||navigator.userAgent),o=r?"\u2318K":"Ctrl+K",a=document.createElement("button");a.className="navbar-action-link site-search-trigger",a.setAttribute("aria-label","Search"),a.setAttribute("type","button");var i=document.createElement("span");if(i.setAttribute("data-icon","search"),a.appendChild(i),g){var d=document.createElement("span");d.className="site-search-shortcut-hint",d.textContent=o,a.appendChild(d)}e.insertBefore(a,e.firstChild),a.addEventListener("click",b),window.Domma&&Domma.icons&&Domma.icons.scan&&Domma.icons.scan(a)}}}function L(){if(document.querySelector("#site-navbar .navbar-actions, .navbar .navbar-actions")){h();return}var t=new MutationObserver(function(){var e=document.querySelector("#site-navbar, .navbar");e&&(t.disconnect(),h())});t.observe(document.body,{childList:!0,subtree:!0}),setTimeout(function(){t.disconnect(),h()},5e3)}function k(){var t=document.createElement("div");t.className="site-search-overlay",t.setAttribute("role","dialog"),t.setAttribute("aria-modal","true"),t.setAttribute("aria-label","Site search");var e=document.createElement("div");e.className="site-search-panel",e.setAttribute("role","search");var n=document.createElement("div");n.className="site-search-header";var r=document.createElement("span");r.setAttribute("data-icon","search"),n.appendChild(r),s=document.createElement("input"),s.className="site-search-input",s.type="search",s.setAttribute("autocomplete","off"),s.setAttribute("autocorrect","off"),s.setAttribute("spellcheck","false"),s.setAttribute("aria-label","Search"),s.placeholder=N,n.appendChild(s);var o=document.createElement("button");o.className="site-search-close-btn",o.setAttribute("type","button"),o.setAttribute("aria-label","Close search"),o.textContent="Esc",n.appendChild(o),e.appendChild(n),c=document.createElement("div"),c.className="site-search-results",c.setAttribute("role","listbox"),c.setAttribute("aria-live","polite"),e.appendChild(c);var a=document.createElement("div");return a.className="site-search-footer",a.innerHTML="<span><kbd>\u2191</kbd><kbd>\u2193</kbd> navigate</span><span><kbd>\u21B5</kbd> open</span><span><kbd>Esc</kbd> close</span>",e.appendChild(a),t.appendChild(e),t.addEventListener("click",function(i){i.target===t&&v()}),o.addEventListener("click",v),s.addEventListener("input",function(){clearTimeout(p);var i=s.value.trim();if(!i){E();return}p=setTimeout(function(){T(i)},w)}),s.addEventListener("keydown",q),window.Domma&&Domma.icons&&Domma.icons.scan&&Domma.icons.scan(n),t}function b(){f||(f=!0,u=k(),document.body.appendChild(u),document.body.style.overflow="hidden",setTimeout(function(){s&&s.focus()},30))}function v(){f&&(f=!1,clearTimeout(p),u&&u.parentNode&&u.parentNode.removeChild(u),u=null,s=null,c=null,document.body.style.overflow="")}function T(t){c&&(x(),fetch("/api/plugins/site-search/search?q="+encodeURIComponent(t)).then(function(e){return e.json()}).then(function(e){M(e.results||[],t)}).catch(function(){E();var e=document.createElement("div");e.className="site-search-empty",e.textContent="Search unavailable. Please try again.",c.appendChild(e)}))}function x(){c&&(c.innerHTML='<div class="site-search-loading"><div class="site-search-loading-dots"><span></span><span></span><span></span></div></div>')}function E(){c&&(c.innerHTML="")}function M(t,e){if(c){if(c.innerHTML="",!t.length){var n=document.createElement("div");n.className="site-search-empty",n.textContent='No results found for "'+e+'"',c.appendChild(n);return}for(var r=document.createDocumentFragment(),o=0;o<t.length;o++){var a=t[o],i=document.createElement("a");i.className="site-search-result",i.href=a.url||"/",i.setAttribute("role","option"),i.setAttribute("tabindex","-1"),i.setAttribute("data-index",String(o));var d=document.createElement("div");if(d.className="site-search-result-title",C(d,a.title||"Untitled",e),i.appendChild(d),a.snippet){var m=document.createElement("div");m.className="site-search-result-snippet",C(m,a.snippet,e),i.appendChild(m)}i.addEventListener("click",v),r.appendChild(i)}c.appendChild(r)}}function C(t,e,n){if(!n){t.textContent=e;return}var r=n.trim().split(/\s+/).filter(Boolean);if(!r.length){t.textContent=e;return}var o;try{o=new RegExp("("+r.map(function(m){return m.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}).join("|")+")","gi")}catch{t.textContent=e;return}for(var a=e.split(o),i=0;i<a.length;i++){if(o.test(a[i])){var d=document.createElement("mark");d.textContent=a[i],t.appendChild(d)}else t.appendChild(document.createTextNode(a[i]));o.lastIndex=0}}function q(t){if(t.key==="Escape"){t.preventDefault(),v();return}if(c){var e=c.querySelectorAll(".site-search-result");if(e.length){for(var n=c.querySelector(".site-search-result.active"),r=-1,o=0;o<e.length;o++)if(e[o]===n){r=o;break}if(t.key==="ArrowDown")t.preventDefault(),A(e,r<e.length-1?r+1:0);else if(t.key==="ArrowUp")t.preventDefault(),A(e,r>0?r-1:e.length-1);else if(t.key==="Enter"&&n){t.preventDefault();var a=n.getAttribute("href");v(),window.location.href=a}}}}function A(t,e){for(var n=0;n<t.length;n++)t[n].classList.toggle("active",n===e);t[e]&&t[e].scrollIntoView({block:"nearest"})}g&&document.addEventListener("keydown",function(t){if((t.metaKey||t.ctrlKey)&&t.key==="k"){var e=document.activeElement&&document.activeElement.tagName;if((e==="INPUT"||e==="TEXTAREA"||e==="SELECT")&&!f)return;t.preventDefault(),f?v():b()}});function O(t,e,n,r){var o=e==="left-center"||e==="bottom-left"||e==="top-left",a=e==="bottom-left"||e==="bottom-right",i=e==="top-left"||e==="top-right";Object.assign(t.style,{right:o?"auto":n+"px",left:o?n+"px":"auto",top:i?r+"px":a?"auto":"50vh",bottom:a?r+"px":"auto",transform:i||a?"none":"translateY(-50%)"})}function H(){if(!document.querySelector(".site-search-floating-trigger")){var t=l.position||"bottom-right",e=l.offsetX!=null?l.offsetX:32,n=l.offsetY!=null?l.offsetY:32,r=document.createElement("button");r.className="site-search-floating-trigger"+(y?"":" site-search-floating-trigger--pill"),r.setAttribute("type","button"),r.setAttribute("aria-label","Search");var o=document.createElement("span");if(o.setAttribute("data-icon","search"),r.appendChild(o),!y){var a=document.createElement("span");a.className="site-search-floating-label",a.textContent="Search",r.appendChild(a)}O(r,t,e,n),document.body.appendChild(r),r.addEventListener("click",b),window.Domma&&Domma.icons&&Domma.icons.scan&&Domma.icons.scan(r)}}function S(){D==="floating"?H():L()}document.readyState==="loading"?document.addEventListener("DOMContentLoaded",S):S()})();
@@ -1,47 +0,0 @@
1
- <div class="survey-audience-plugin">
2
-
3
- <div class="toolbar mb-3" style="display:flex;align-items:center;gap:0.5rem;flex-wrap:wrap;">
4
- <h2 style="margin:0;flex:1;">Survey Audience</h2>
5
- <button id="back-surveys" class="btn btn-outline">
6
- <span data-icon="arrow-left"></span>
7
- Surveys
8
- </button>
9
- <button id="import-contacts" class="btn btn-outline">
10
- <span data-icon="download"></span>
11
- Import from Contacts
12
- </button>
13
- <button id="add-contact" class="btn btn-primary">
14
- <span data-icon="plus"></span>
15
- Add contact
16
- </button>
17
- </div>
18
-
19
- <div class="grid grid-cols-3" style="gap:1rem;align-items:start;">
20
-
21
- <!-- Contacts table -->
22
- <div style="grid-column:span 2;">
23
- <div id="audience-table"></div>
24
- <div id="audience-empty" style="display:none;text-align:center;padding:2rem 1rem;color:var(--dm-text-muted);">
25
- <p>No audience contacts yet. Add one or import from the Contacts plugin.</p>
26
- </div>
27
- </div>
28
-
29
- <!-- Groups panel -->
30
- <div class="card">
31
- <div class="card-header"><strong>Groups</strong></div>
32
- <div class="card-body">
33
- <div style="display:flex;gap:0.5rem;margin-bottom:1rem;">
34
- <input id="new-group-name" type="text" class="form-input" placeholder="New group name…" style="flex:1;">
35
- <button id="add-group" class="btn btn-primary">
36
- <span data-icon="plus"></span>
37
- </button>
38
- </div>
39
- <div id="groups-list">
40
- <p class="text-muted">No groups yet.</p>
41
- </div>
42
- </div>
43
- </div>
44
-
45
- </div>
46
-
47
- </div>
@@ -1,58 +0,0 @@
1
- <div class="survey-results-plugin">
2
-
3
- <style>
4
- /* Layout only + real Domma theme tokens - surfaces use .card so the
5
- active theme is always respected; no hardcoded colours. */
6
- .survey-results-plugin .sr-head { display:flex; align-items:flex-start; gap:1rem; flex-wrap:wrap; margin-bottom:1.25rem; }
7
- .survey-results-plugin .sr-head h2 { margin:0 0 0.25rem; }
8
- .survey-results-plugin .sr-sub { color:var(--dm-text-muted); margin:0; }
9
- .survey-results-plugin .sr-head-actions { margin-left:auto; display:flex; gap:0.5rem; flex-wrap:wrap; }
10
-
11
- .survey-results-plugin .sr-summary { display:grid; grid-template-columns:1.4fr 1fr 1fr 1fr; gap:1rem; margin-bottom:1.5rem; }
12
- @media (max-width:760px){ .survey-results-plugin .sr-summary { grid-template-columns:1fr 1fr; } }
13
- .survey-results-plugin .sr-rate-num { font-size:2.4rem; font-weight:800; line-height:1; color:var(--dm-primary); }
14
- .survey-results-plugin .sr-kpi-num { font-size:2rem; font-weight:800; line-height:1; }
15
- .survey-results-plugin .sr-kpi-lbl { color:var(--dm-text-muted); font-size:0.85rem; margin-top:0.35rem; }
16
- .survey-results-plugin .sr-rate-bar { height:8px; border-radius:999px; background:var(--dm-border); margin-top:0.75rem; overflow:hidden; }
17
- .survey-results-plugin .sr-rate-fill { height:100%; background:var(--dm-primary); border-radius:999px; }
18
-
19
- .survey-results-plugin .sr-section-title { font-size:1.05rem; font-weight:700; margin:0 0 0.75rem; }
20
- .survey-results-plugin .sr-q-head { display:flex; align-items:baseline; gap:0.6rem; margin-bottom:0.75rem; }
21
- .survey-results-plugin .sr-q-num { display:inline-flex; align-items:center; justify-content:center; min-width:1.6rem; height:1.6rem; border-radius:50%; background:var(--dm-primary); color:var(--dm-text-inverse); font-size:0.8rem; font-weight:700; }
22
- .survey-results-plugin .sr-q-label { font-weight:700; }
23
- .survey-results-plugin .sr-q-meta { margin-left:auto; color:var(--dm-text-muted); font-size:0.8rem; text-transform:uppercase; letter-spacing:0.03em; }
24
- .survey-results-plugin .sr-avg { font-size:1.1rem; margin:0 0 0.6rem; }
25
- .survey-results-plugin .sr-avg strong { font-size:1.5rem; color:var(--dm-primary); }
26
-
27
- /* Distribution bars (survey.css isn't loaded in admin, so style here). */
28
- .survey-results-plugin .survey-dist { display:flex; flex-direction:column; gap:0.5rem; }
29
- .survey-results-plugin .survey-dist-row { display:grid; grid-template-columns:minmax(80px,160px) 1fr auto; align-items:center; gap:0.75rem; }
30
- .survey-results-plugin .survey-dist-label { font-size:0.9rem; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
31
- .survey-results-plugin .survey-dist-bar { height:14px; background:var(--dm-border); border-radius:999px; overflow:hidden; }
32
- .survey-results-plugin .survey-dist-fill { height:100%; background:var(--dm-primary); border-radius:999px; min-width:2px; }
33
- .survey-results-plugin .survey-dist-val { font-size:0.85rem; color:var(--dm-text-muted); white-space:nowrap; }
34
-
35
- /* Free-text answers. */
36
- .survey-results-plugin .sr-quotes { max-height:260px; overflow:auto; display:flex; flex-direction:column; gap:0.5rem; }
37
- .survey-results-plugin .sr-quote { padding:0.5rem 0.75rem; background:var(--dm-surface-raised); border-left:3px solid var(--dm-border); border-radius:0 6px 6px 0; font-size:0.92rem; }
38
- .survey-results-plugin .sr-empty { color:var(--dm-text-muted); font-style:italic; }
39
- </style>
40
-
41
- <div class="sr-head">
42
- <div>
43
- <h2 id="results-title">Survey Results</h2>
44
- <p class="sr-sub" id="results-sub">How your audience responded.</p>
45
- </div>
46
- <div class="sr-head-actions">
47
- <button id="export-csv" class="btn btn-outline"><span data-icon="download"></span> CSV</button>
48
- <button id="export-json" class="btn btn-outline"><span data-icon="download"></span> JSON</button>
49
- <button id="back-surveys" class="btn btn-outline"><span data-icon="arrow-left"></span> Surveys</button>
50
- </div>
51
- </div>
52
-
53
- <div id="summary" class="sr-summary"></div>
54
-
55
- <h3 class="sr-section-title">Question breakdown</h3>
56
- <div id="questions"></div>
57
-
58
- </div>
@@ -1,141 +0,0 @@
1
- <div class="survey-editor-plugin">
2
-
3
- <style>
4
- /* Layout only - colours/borders come from Domma's themed .card and
5
- .form-input, so the active theme is always respected. */
6
- .survey-editor-plugin .se-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 1rem 1.25rem; }
7
- .survey-editor-plugin .se-span-2 { grid-column: 1 / -1; }
8
- .survey-editor-plugin .se-field { display: flex; flex-direction: column; gap: 0.35rem; }
9
- .survey-editor-plugin .se-field label { font-weight: 600; font-size: 0.85rem; }
10
- .survey-editor-plugin .se-field .se-hint { font-size: 0.78rem; color: var(--dm-text-muted); }
11
- .survey-editor-plugin .se-field .form-input { width: 100%; }
12
- .survey-editor-plugin .se-field textarea.form-input { min-height: 90px; resize: vertical; }
13
- .survey-editor-plugin .se-field select[multiple].form-input { min-height: 120px; }
14
- @media (max-width: 640px) { .survey-editor-plugin .se-grid { grid-template-columns: 1fr; } }
15
- </style>
16
-
17
- <div class="toolbar mb-3" style="display:flex;align-items:center;gap:0.5rem;">
18
- <h2 id="editor-title" style="margin:0;flex:1;">New Survey</h2>
19
- <button id="back-btn" class="btn btn-outline"><span data-icon="arrow-left"></span> Back</button>
20
- </div>
21
-
22
- <div class="card">
23
- <div class="card-header"><strong>Basics</strong></div>
24
- <div class="card-body">
25
- <div class="se-grid">
26
- <div class="se-field se-span-2">
27
- <label for="f-title">Title</label>
28
- <input id="f-title" name="title" type="text" class="form-input" placeholder="Customer satisfaction Q3">
29
- </div>
30
- <div class="se-field">
31
- <label for="f-slug">Slug</label>
32
- <input id="f-slug" name="slug" type="text" class="form-input" placeholder="customer-sat-q3">
33
- <span class="se-hint" id="slug-hint">URL-safe identifier.</span>
34
- </div>
35
- <div class="se-field">
36
- <label for="f-formSlug">Form</label>
37
- <select id="f-formSlug" name="formSlug" class="form-input"><option value="">Select a form…</option></select>
38
- <span class="se-hint">The Forms definition respondents complete.</span>
39
- </div>
40
- <div class="se-field se-span-2">
41
- <label for="f-description">Description</label>
42
- <textarea id="f-description" name="description" class="form-input" placeholder="Optional internal description…"></textarea>
43
- </div>
44
- </div>
45
- </div>
46
- </div>
47
-
48
- <div class="card mt-3">
49
- <div class="card-header"><strong>Setup</strong></div>
50
- <div class="card-body">
51
- <div class="se-grid">
52
- <div class="se-field">
53
- <label for="f-status">Status</label>
54
- <select id="f-status" name="status" class="form-input">
55
- <option value="draft">Draft</option>
56
- <option value="open">Open</option>
57
- <option value="closed">Closed</option>
58
- </select>
59
- </div>
60
- <div class="se-field">
61
- <label for="f-mode">Mode</label>
62
- <select id="f-mode" name="mode" class="form-input">
63
- <option value="identified">Identified</option>
64
- <option value="anonymous">Anonymous</option>
65
- </select>
66
- <span class="se-hint">Anonymous de-links responses from contacts.</span>
67
- </div>
68
- <div class="se-field se-span-2">
69
- <label for="f-groupIds">Audience groups</label>
70
- <select id="f-groupIds" name="groupIds" class="form-input" multiple></select>
71
- <span class="se-hint">Hold Ctrl/Cmd to select multiple groups.</span>
72
- </div>
73
- </div>
74
- </div>
75
- </div>
76
-
77
- <div class="card mt-3">
78
- <div class="card-header"><strong>Schedule</strong></div>
79
- <div class="card-body">
80
- <div class="se-grid">
81
- <div class="se-field"><label for="f-opensAt">Opens at</label><input id="f-opensAt" name="opensAt" type="date" class="form-input"></div>
82
- <div class="se-field"><label for="f-closesAt">Closes at</label><input id="f-closesAt" name="closesAt" type="date" class="form-input"></div>
83
- <div class="se-field"><label for="f-reminderAfterDays">Reminder after (days)</label><input id="f-reminderAfterDays" name="reminderAfterDays" type="number" min="0" value="7" class="form-input"></div>
84
- </div>
85
- </div>
86
- </div>
87
-
88
- <div class="card mt-3">
89
- <div class="card-header"><strong>Survey page content</strong></div>
90
- <div class="card-body">
91
- <div class="se-grid">
92
- <div class="se-field se-span-2">
93
- <label for="f-intro">Intro (Markdown - shown above the form)</label>
94
- <textarea id="f-intro" name="intro" class="form-input" placeholder="## Welcome&#10;Thanks for taking a moment to help us improve."></textarea>
95
- </div>
96
- <div class="se-field se-span-2">
97
- <label for="f-footer">Footer (Markdown - shown below the form)</label>
98
- <textarea id="f-footer" name="footer" class="form-input" placeholder="Questions? Contact us at hello@example.com"></textarea>
99
- </div>
100
- <div class="se-field se-span-2">
101
- <label for="f-thankYou">Thank-you message (shown after submitting)</label>
102
- <textarea id="f-thankYou" name="thankYou" class="form-input" placeholder="Thank you - your response has been recorded."></textarea>
103
- </div>
104
- </div>
105
- </div>
106
- </div>
107
-
108
- <div class="card mt-3">
109
- <div class="card-header"><strong>Invitation email</strong></div>
110
- <div class="card-body">
111
- <div class="se-grid">
112
- <div class="se-field se-span-2">
113
- <label for="f-inviteSubject">Subject</label>
114
- <input id="f-inviteSubject" name="inviteSubject" type="text" class="form-input" placeholder="We would love your feedback">
115
- </div>
116
- <div class="se-field se-span-2">
117
- <label for="f-inviteBody">Body</label>
118
- <textarea id="f-inviteBody" name="inviteBody" class="form-input" placeholder="Hi {{name}}, please complete our survey: {{link}}"></textarea>
119
- <span class="se-hint">{{name}} and {{link}} are substituted per recipient; {{link}} becomes the button.</span>
120
- </div>
121
- </div>
122
- </div>
123
- </div>
124
-
125
- <div class="card mt-3">
126
- <div class="card-body" style="display:flex;gap:0.5rem;flex-wrap:wrap;">
127
- <button id="save-btn" class="btn btn-primary">Save Survey</button>
128
- <button id="cancel-btn" class="btn btn-outline">Cancel</button>
129
- </div>
130
- </div>
131
-
132
- <div id="send-panel" class="card mt-3" style="display:none;">
133
- <div class="card-header"><strong>Invitations</strong></div>
134
- <div class="card-body" style="display:flex;gap:0.5rem;flex-wrap:wrap;align-items:center;">
135
- <button id="send-all-btn" class="btn btn-primary"><span data-icon="send"></span> Send invitations</button>
136
- <button id="send-reminder-btn" class="btn btn-outline"><span data-icon="bell"></span> Send reminder to non-responders</button>
137
- <span id="send-hint" class="text-muted" style="font-size:0.85rem;"></span>
138
- </div>
139
- </div>
140
-
141
- </div>
@@ -1,25 +0,0 @@
1
- <div class="surveys-plugin">
2
-
3
- <!-- Toolbar -->
4
- <div class="toolbar mb-3" style="display:flex;align-items:center;gap:0.5rem;flex-wrap:wrap;">
5
- <h2 style="margin:0;flex:1;">Surveys</h2>
6
- <button id="manage-audience" class="btn btn-outline">
7
- <span data-icon="users"></span>
8
- Audience
9
- </button>
10
- <button id="new-survey" class="btn btn-primary">
11
- <span data-icon="plus"></span>
12
- New Survey
13
- </button>
14
- </div>
15
-
16
- <!-- Surveys table -->
17
- <div id="surveys-table"></div>
18
-
19
- <!-- Empty state -->
20
- <div id="surveys-empty" style="display:none;text-align:center;padding:3rem 1rem;color:var(--dm-text-muted);">
21
- <span data-icon="clipboard-list" style="font-size:3rem;display:block;margin-bottom:1rem;"></span>
22
- <p>No surveys yet. Click <strong>New Survey</strong> to create your first campaign.</p>
23
- </div>
24
-
25
- </div>