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
@@ -0,0 +1,138 @@
1
+ <!-- No heading: the banner above (admin/js/lib/plugin-chrome.js) is the title.
2
+ Everything that changes is bound (M.applyBindings) - see admin/js/views/analytics.js, which also
3
+ inserts the list-row templates: the router renders this file through _.render, eating any {{…}}. -->
4
+ <div class="an-tool">
5
+
6
+ <div class="an-bar">
7
+ <select class="form-input an-range" aria-label="Range" data-model="range.value">
8
+ <option value="today">Today</option>
9
+ <option value="7d">Last 7 days</option>
10
+ <option value="30d">Last 30 days</option>
11
+ <option value="90d">Last 90 days</option>
12
+ <option value="all">All time</option>
13
+ </select>
14
+ <span class="an-live" data-bind-class="liveClass.value" data-bind-title="liveTitle.value">
15
+ <span class="an-live-dot"></span><span data-bind-text="liveText.value"></span>
16
+ </span>
17
+ <span data-help-title="Active now"
18
+ data-help="Visitors with a page view in the last five minutes, counted by browser session. It refreshes itself every 30 seconds while this screen is open (switch that off in settings)."></span>
19
+ <div class="an-bar-end">
20
+ <button type="button" class="btn btn-sm btn-ghost" data-on-click="refresh" title="Refresh">
21
+ <span data-icon="refresh-cw" data-icon-size="14"></span>
22
+ </button>
23
+ <button type="button" class="btn btn-sm btn-ghost an-more" aria-label="More actions" title="Export, reset">⋮</button>
24
+ </div>
25
+ </div>
26
+
27
+ <div class="an-kpis">
28
+ <div class="an-kpi"><span class="an-kpi-value" data-bind-text="kpi.value.hits">-</span>
29
+ <span class="an-kpi-label">Page views <span data-help="Every page view in the chosen range. Admin pages, the API, and visitors whose browser asks not to be tracked are never counted."></span></span></div>
30
+ <div class="an-kpi"><span class="an-kpi-value" data-bind-text="kpi.value.pages">-</span>
31
+ <span class="an-kpi-label">Pages viewed</span></div>
32
+ <div class="an-kpi"><span class="an-kpi-value" data-bind-text="kpi.value.sessions">-</span>
33
+ <span class="an-kpi-label">Sessions <span data-help="Browser sessions seen in the range (up to 30 days back). One visitor in one browser tab session counts once, however many pages they read."></span></span></div>
34
+ <div class="an-kpi"><span class="an-kpi-value" data-bind-text="kpi.value.bounce">-</span>
35
+ <span class="an-kpi-label">Bounce rate <span data-help="The share of sessions that viewed a single page and left."></span></span></div>
36
+ <div class="an-kpi"><span class="an-kpi-value" data-bind-text="kpi.value.lifetime">-</span>
37
+ <span class="an-kpi-label">All-time views</span></div>
38
+ </div>
39
+
40
+ <div class="an-chart-wrap">
41
+ <canvas class="an-chart" aria-label="Page views over time"></canvas>
42
+ <p class="an-note" data-if="isAllTime.value">The chart shows the last 30 days; the figures above are all time.</p>
43
+ </div>
44
+
45
+ <div class="an-tabs" role="tablist">
46
+ <button type="button" role="tab" data-on-click="showPages" data-bind-class="section.value === 'pages' && 'is-active'">Top pages</button>
47
+ <button type="button" role="tab" data-on-click="showJourneys" data-bind-class="section.value === 'journeys' && 'is-active'">Journeys</button>
48
+ <button type="button" role="tab" data-on-click="showSpikes" data-bind-class="section.value === 'spikes' && 'is-active'">
49
+ Spikes <span class="an-count" data-if="spikes.value.length" data-bind-text="spikes.value.length"></span></button>
50
+ <span data-help-title="Right-click a page"
51
+ data-help="Every page in these lists has a menu - right-click it, or use its ⋮ - to open it on the site, copy its address, or narrow the list to it."></span>
52
+ <label class="an-filter" data-if="section.value === 'pages'">
53
+ <span data-icon="search" data-icon-size="13"></span>
54
+ <input type="search" class="form-input" placeholder="Filter pages…" aria-label="Filter pages" data-model="filter.value">
55
+ </label>
56
+ </div>
57
+
58
+ <section class="an-panel" data-if="section.value === 'pages'">
59
+ <div class="an-list" data-each="pageRows.value key=key"></div>
60
+ <p class="an-note" data-bind-text="pagesNote.value"></p>
61
+ </section>
62
+
63
+ <section class="an-panel an-journeys" data-if="section.value === 'journeys'">
64
+ <div><h3>Entry pages <span data-help="Where sessions started."></span></h3>
65
+ <div class="an-list" data-each="entryRows.value key=key"></div></div>
66
+ <div><h3>Exit pages <span data-help="The last page a session viewed."></span></h3>
67
+ <div class="an-list" data-each="exitRows.value key=key"></div></div>
68
+ <div class="an-wide"><h3>Common paths <span data-help="The most frequent step from one page straight to another."></span></h3>
69
+ <div class="an-list" data-each="pathRows.value key=key"></div></div>
70
+ <p class="an-note" data-if="!sessionsCount.value">No sessions recorded in this range yet.</p>
71
+ </section>
72
+
73
+ <section class="an-panel" data-if="section.value === 'spikes'">
74
+ <div class="an-list" data-each="spikes.value key=key"></div>
75
+ <p class="an-note" data-if="!spikes.value.length">Nothing unusual today. A spike is a page running well above its last seven days at this hour.</p>
76
+ </section>
77
+ </div>
78
+
79
+ <style>
80
+ .an-tool { display: flex; flex-direction: column; gap: .9rem; }
81
+ .an-bar { display: flex; align-items: center; gap: .6rem; flex-wrap: wrap; }
82
+ .an-range { width: auto; height: 2rem; padding-top: 0; padding-bottom: 0; }
83
+ .an-live { display: inline-flex; align-items: center; gap: .4rem; font-size: var(--dm-text-xs); color: var(--dm-text-muted);
84
+ padding: .15rem .6rem; border-radius: 999px; background: var(--dm-surface-hover, rgba(127,127,127,.12)); }
85
+ .an-live-dot { width: .5rem; height: .5rem; border-radius: 50%; background: var(--dm-text-muted); }
86
+ .an-live.is-on .an-live-dot { background: var(--dm-success, #16a34a); box-shadow: 0 0 0 3px color-mix(in srgb, var(--dm-success, #16a34a) 25%, transparent); }
87
+ .an-bar-end { display: flex; gap: .3rem; margin-left: auto; }
88
+ .an-more { font-size: var(--dm-text-lg); line-height: 1; padding: .2rem .5rem; }
89
+
90
+ .an-kpis { display: grid; grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr)); gap: .6rem; }
91
+ .an-kpi { display: flex; flex-direction: column; gap: .15rem; padding: .6rem .8rem;
92
+ border: 1px solid var(--dm-border); border-radius: var(--dm-radius-md); }
93
+ .an-kpi-value { font-size: var(--dm-text-xl); font-weight: 600; font-variant-numeric: tabular-nums; }
94
+ .an-kpi-label { font-size: var(--dm-text-xs); color: var(--dm-text-muted); display: inline-flex; align-items: center; }
95
+
96
+ .an-chart-wrap { position: relative; height: 220px; padding-bottom: 1.2rem; }
97
+ .an-chart-wrap .an-note { position: absolute; bottom: 0; left: 0; margin: 0; }
98
+
99
+ .an-tabs { display: flex; align-items: center; gap: .25rem; border-bottom: 1px solid var(--dm-border); flex-wrap: wrap; }
100
+ .an-tabs [role="tab"] { border: 0; background: none; cursor: pointer; font: inherit; font-size: var(--dm-text-sm);
101
+ padding: .45rem .7rem; color: var(--dm-text-muted); border-bottom: 2px solid transparent; margin-bottom: -1px; }
102
+ .an-tabs [role="tab"].is-active { color: var(--dm-text); border-bottom-color: var(--dm-primary); }
103
+ .an-count { font-size: var(--dm-text-xs); padding: 0 .4rem; border-radius: 999px; background: var(--dm-warning, #d97706); color: #fff; }
104
+ .an-filter { position: relative; margin: 0 0 .3rem auto; }
105
+ .an-filter > svg, .an-filter > [data-icon] { position: absolute; left: .55rem; top: 50%; transform: translateY(-50%); color: var(--dm-text-muted); pointer-events: none; }
106
+ .an-filter .form-input { height: 1.85rem; padding-left: 1.75rem; width: 14rem; }
107
+
108
+ .an-panel { display: flex; flex-direction: column; gap: .5rem; }
109
+ .an-journeys { display: grid; grid-template-columns: 1fr 1fr; gap: 1rem; }
110
+ .an-journeys .an-wide, .an-journeys .an-note { grid-column: 1 / -1; }
111
+ .an-journeys h3 { margin: 0 0 .35rem; font-size: var(--dm-text-xs); font-weight: 600; color: var(--dm-text-muted);
112
+ text-transform: uppercase; letter-spacing: .04em; display: flex; align-items: center; }
113
+ @media (max-width: 760px) { .an-journeys { grid-template-columns: 1fr; } }
114
+
115
+ .an-list { display: flex; flex-direction: column; }
116
+ .an-row { position: relative; display: flex; align-items: center; gap: .6rem; padding: .3rem .4rem .3rem .6rem;
117
+ font-size: var(--dm-text-sm); border-radius: var(--dm-radius-sm); isolation: isolate; }
118
+ .an-row:hover { background: var(--dm-surface-hover, rgba(127,127,127,.08)); }
119
+ .an-bar-fill { position: absolute; inset: 2px auto 2px 0; z-index: -1; border-radius: var(--dm-radius-sm);
120
+ background: color-mix(in srgb, var(--dm-primary) 14%, transparent); }
121
+ .an-rank { flex: 0 0 1.6rem; color: var(--dm-text-muted); font-size: var(--dm-text-xs); text-align: right; }
122
+ .an-url { flex: 1; min-width: 0; font-family: var(--dm-font-mono, monospace); font-size: var(--dm-text-xs);
123
+ white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
124
+ .an-num { flex: 0 0 auto; font-variant-numeric: tabular-nums; }
125
+ .an-share { flex: 0 0 3.2rem; text-align: right; font-size: var(--dm-text-xs); color: var(--dm-text-muted); }
126
+ .an-kebab { flex: 0 0 auto; border: 0; background: none; cursor: pointer; color: var(--dm-text-muted);
127
+ font-size: var(--dm-text-lg); line-height: 1; padding: 0 .3rem; border-radius: var(--dm-radius-sm); }
128
+ .an-kebab:hover { color: var(--dm-primary); }
129
+ .an-note { margin: .25rem 0; font-size: var(--dm-text-xs); color: var(--dm-text-muted); }
130
+ .an-note:empty { display: none; }
131
+
132
+ .an-panel-form { display: flex; flex-direction: column; gap: 1rem; padding: .25rem 0 .5rem; }
133
+ .an-panel-form .form-label { display: flex; align-items: center; font-weight: 500; margin-bottom: .3rem; }
134
+ .an-setting-row { display: flex; align-items: center; justify-content: space-between; gap: 1rem; }
135
+ .an-panel-meta { font-size: var(--dm-text-xs); color: var(--dm-text-muted); }
136
+ .an-panel-actions { display: flex; align-items: center; gap: .5rem; padding-top: .75rem; border-top: 1px solid var(--dm-border); }
137
+ .an-panel-actions .spacer { flex: 1; }
138
+ </style>
@@ -0,0 +1,156 @@
1
+ <!-- No heading: the banner above (admin/js/lib/plugin-chrome.js) is the title.
2
+ Everything that changes is bound (M.applyBindings) - see admin/js/views/contacts.js, which also
3
+ inserts the row and chip templates: the router renders this file through _.render, eating any {{…}}.
4
+ The editor, settings and groups panels are slideovers built in the view. -->
5
+ <div class="ct-tool">
6
+
7
+ <div class="ct-bar">
8
+ <label class="ct-search">
9
+ <span data-icon="search" data-icon-size="14"></span>
10
+ <input type="search" class="form-input" placeholder="Search contacts…" aria-label="Search contacts"
11
+ data-model="query.value" data-focus="searchFocus.value">
12
+ </label>
13
+ <span class="ct-count" data-bind-text="countText.value"></span>
14
+ <span data-help-title="Working with contacts"
15
+ data-help="Search matches names, emails, phones, companies, notes and groups. Right-click a contact (or its ⋮) to edit, email, call, copy, star, file it in a group, duplicate or delete it. Click a row to edit. Press / to search, N for a new contact."></span>
16
+ <span class="ct-spacer"></span>
17
+ <button type="button" class="btn btn-sm btn-primary" data-on-click="create">
18
+ <span data-icon="plus" data-icon-size="14"></span> New contact
19
+ </button>
20
+ <button type="button" class="btn btn-sm btn-ghost ct-more" aria-label="More actions" title="Export, import, groups">⋮</button>
21
+ </div>
22
+
23
+ <!-- Everyone / starred / each group, with counts: the filter and the stats in one row. -->
24
+ <div class="ct-chips-row">
25
+ <div class="ct-chips" data-each="chips.value key=key"></div>
26
+ <span data-help-title="Filter by group"
27
+ data-help="The numbers are how many contacts are in each. Click one to show only those; click All to show everyone. ⋮ > Manage groups renames or deletes a group."></span>
28
+ </div>
29
+
30
+ <div class="ct-bulk" data-if="hasSelection.value">
31
+ <strong data-bind-text="selectionText.value"></strong>
32
+ <button type="button" class="btn btn-sm btn-ghost ct-bulk-group">
33
+ <span data-icon="folder-plus" data-icon-size="13"></span> Add to group ▾
34
+ </button>
35
+ <button type="button" class="btn btn-sm btn-ghost text-danger" data-on-click="bulkDelete">
36
+ <span data-icon="trash" data-icon-size="13"></span> Delete
37
+ </button>
38
+ <button type="button" class="btn btn-sm btn-ghost" data-on-click="clearSelection">Clear</button>
39
+ </div>
40
+
41
+ <p class="ct-state" data-if="loading.value">Loading contacts…</p>
42
+ <p class="ct-state" data-if="isEmpty.value" data-bind-text="emptyText.value"></p>
43
+
44
+ <div class="ct-list">
45
+ <div class="ct-head" data-if="hasRows.value">
46
+ <input type="checkbox" class="ct-select-all" aria-label="Select every contact shown" title="Select all shown"
47
+ data-bind-checked="allSelected.value">
48
+ <span></span>
49
+ <span class="ct-h-name">Name</span>
50
+ <span class="ct-col ct-company">Company</span>
51
+ <span class="ct-col ct-email">Email</span>
52
+ <span class="ct-col ct-phone">Phone</span>
53
+ <span class="ct-col ct-groups">Groups
54
+ <span data-help="Groups this contact is filed in. Change them from the contact's menu (Groups) or in the editor."></span></span>
55
+ <span></span>
56
+ </div>
57
+ <div class="ct-rows" data-each="rows.value key=id"></div>
58
+ </div>
59
+
60
+ <input type="file" class="ct-import-file" accept=".json,application/json" hidden>
61
+ </div>
62
+
63
+ <style>
64
+ .ct-tool { display: flex; flex-direction: column; gap: .75rem; }
65
+ .ct-bar { display: flex; align-items: center; gap: .5rem; flex-wrap: wrap; }
66
+ .ct-search { position: relative; flex: 1 1 200px; max-width: 320px; margin: 0; }
67
+ .ct-search > svg, .ct-search > [data-icon] { position: absolute; left: .6rem; top: 50%; transform: translateY(-50%);
68
+ color: var(--dm-text-muted); pointer-events: none; }
69
+ .ct-search .form-input { height: 2rem; padding-left: 1.9rem; }
70
+ .ct-count { font-size: var(--dm-text-xs); color: var(--dm-text-muted); }
71
+ .ct-spacer { flex: 1; }
72
+ .ct-more { font-size: var(--dm-text-lg); line-height: 1; padding: .2rem .5rem; }
73
+
74
+ .ct-chips-row { display: flex; align-items: center; gap: .35rem; flex-wrap: wrap; }
75
+ .ct-chips { display: flex; gap: .35rem; flex-wrap: wrap; }
76
+ .ct-chip { display: inline-flex; align-items: center; gap: .35rem; padding: .15rem .65rem; border-radius: 999px; cursor: pointer;
77
+ border: 1px solid var(--dm-border); background: none; color: var(--dm-text-muted); font: inherit; font-size: var(--dm-text-xs); }
78
+ .ct-chip:hover { border-color: var(--dm-primary); color: var(--dm-text); }
79
+ .ct-chip.is-active { background: var(--dm-primary); border-color: var(--dm-primary); color: var(--dm-primary-text, #fff); }
80
+ .ct-chip b { font-weight: 600; font-variant-numeric: tabular-nums; }
81
+
82
+ .ct-bulk { display: flex; align-items: center; gap: .5rem; padding: .4rem .75rem; font-size: var(--dm-text-sm);
83
+ border-radius: var(--dm-radius-md); background: color-mix(in srgb, var(--dm-primary) 10%, transparent); }
84
+ .ct-state { margin: 1.5rem 0; color: var(--dm-text-muted); text-align: center; }
85
+
86
+ .ct-list { border: 1px solid var(--dm-border); border-radius: var(--dm-radius-md); overflow: hidden; }
87
+ .ct-list:has(.ct-rows:empty) { border: 0; }
88
+ .ct-head, .ct-row {
89
+ display: grid; align-items: center; gap: .75rem; padding: .5rem .75rem;
90
+ /* Set by the view from the column settings. */
91
+ grid-template-columns: var(--ct-cols, 1.1rem 1.5rem minmax(10rem, 1.4fr) minmax(0, 1fr) minmax(0, 1.2fr) minmax(0, .9fr) minmax(0, 1fr) 1.75rem);
92
+ }
93
+ .ct-head { font-size: var(--dm-text-xs); text-transform: uppercase; letter-spacing: .04em; color: var(--dm-text-muted);
94
+ border-bottom: 1px solid var(--dm-border); padding-top: .4rem; padding-bottom: .4rem; }
95
+ .ct-head > span { display: inline-flex; align-items: center; min-width: 0; }
96
+ .ct-row { border-bottom: 1px solid var(--dm-border); font-size: var(--dm-text-sm); cursor: pointer; }
97
+ .ct-row:last-child { border-bottom: 0; }
98
+ .ct-row:hover, .ct-row:focus-visible { background: var(--dm-surface-hover, rgba(127,127,127,.08)); outline: none; }
99
+ .ct-row.is-selected { background: color-mix(in srgb, var(--dm-primary) 8%, transparent); }
100
+ .ct-fav { border: 0; background: none; cursor: pointer; padding: 0; font-size: var(--dm-text-base); line-height: 1; color: var(--dm-text-muted); opacity: .35; }
101
+ .ct-fav:hover { opacity: .8; }
102
+ .ct-fav.is-on { color: var(--dm-warning, #d97706); opacity: 1; }
103
+ .ct-name { display: flex; align-items: center; gap: .6rem; min-width: 0; }
104
+ .ct-avatar { flex: 0 0 auto; width: 1.9rem; height: 1.9rem; border-radius: 50%; display: inline-flex; align-items: center; justify-content: center;
105
+ font-size: var(--dm-text-xs); font-weight: 600; background: color-mix(in srgb, var(--dm-primary) 18%, transparent); color: var(--dm-primary); }
106
+ .ct-name-text { display: flex; flex-direction: column; min-width: 0; }
107
+ .ct-name-text strong, .ct-name-text small, .ct-col { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
108
+ .ct-name-text small { color: var(--dm-text-muted); font-size: var(--dm-text-xs); }
109
+ .ct-name-text small:empty { display: none; }
110
+ .ct-col { min-width: 0; color: var(--dm-text-muted); }
111
+ .ct-row .ct-email, .ct-row .ct-phone { color: var(--dm-text); }
112
+ .ct-groups-text { font-size: var(--dm-text-xs); }
113
+ .ct-kebab { border: 0; background: none; cursor: pointer; color: var(--dm-text-muted); font-size: var(--dm-text-base); line-height: 1;
114
+ padding: 0 .3rem; border-radius: var(--dm-radius-sm); }
115
+ .ct-kebab:hover { color: var(--dm-primary); background: var(--dm-surface-hover, rgba(127,127,127,.15)); }
116
+
117
+ /* Density and hidden columns, from settings - classes on the list, so rows never re-render for them. */
118
+ .ct-list.is-compact .ct-row { padding-top: .25rem; padding-bottom: .25rem; }
119
+ .ct-list.is-compact .ct-avatar { width: 1.4rem; height: 1.4rem; font-size: var(--dm-text-xs); }
120
+ .ct-list.is-compact .ct-name-text small { display: none; }
121
+ .ct-list.hide-company .ct-company, .ct-list.hide-email .ct-email,
122
+ .ct-list.hide-phone .ct-phone, .ct-list.hide-groups .ct-groups { display: none; }
123
+ @media (max-width: 820px) {
124
+ .ct-head { display: none; }
125
+ .ct-row { grid-template-columns: 1.1rem 1.5rem 1fr 1.75rem; }
126
+ .ct-row .ct-col { display: none; }
127
+ }
128
+
129
+ /* Slideover panels: editor, settings, groups. */
130
+ .ct-panel { display: flex; flex-direction: column; gap: 1rem; padding: .25rem 0 .5rem; }
131
+ .ct-panel .form-label { display: flex; align-items: center; font-weight: 500; margin-bottom: .3rem; }
132
+ .ct-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(12rem, 1fr)); gap: .75rem 1rem; }
133
+ .ct-grid .ct-wide { grid-column: 1 / -1; }
134
+ .ct-section { border-top: 1px solid var(--dm-border); padding-top: .75rem; }
135
+ .ct-section > summary { cursor: pointer; display: flex; align-items: center; gap: .5rem; font-weight: 600; list-style: none; }
136
+ .ct-section > summary::-webkit-details-marker { display: none; }
137
+ .ct-section > summary::before { content: '▸'; color: var(--dm-text-muted); transition: transform .15s ease; }
138
+ .ct-section[open] > summary::before { transform: rotate(90deg); }
139
+ .ct-section-sum { font-weight: 400; font-size: var(--dm-text-xs); color: var(--dm-text-muted); }
140
+ .ct-section-body { display: flex; flex-direction: column; gap: 1rem; padding-top: .75rem; }
141
+ .ct-subhead { display: flex; align-items: center; justify-content: space-between; gap: 1rem; margin-bottom: .4rem; }
142
+ .ct-subhead strong { display: inline-flex; align-items: center; }
143
+ .ct-pick { display: flex; flex-wrap: wrap; gap: .35rem; }
144
+ .ct-pick .ct-chip { font-size: var(--dm-text-xs); }
145
+ .ct-setting-row { display: flex; align-items: center; justify-content: space-between; gap: 1rem; }
146
+ .ct-panel-meta { font-size: var(--dm-text-xs); color: var(--dm-text-muted); }
147
+ .ct-panel-actions { display: flex; align-items: center; gap: .5rem; padding-top: .75rem; border-top: 1px solid var(--dm-border); }
148
+ .ct-panel-actions .spacer { flex: 1; }
149
+ .ct-group-row { display: flex; align-items: center; gap: .5rem; padding: .4rem .5rem; border-bottom: 1px solid var(--dm-border); font-size: var(--dm-text-sm); }
150
+ .ct-group-row:last-child { border-bottom: 0; }
151
+ .ct-group-row span { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
152
+ .ct-group-row small { color: var(--dm-text-muted); }
153
+ .ct-groups-list { border: 1px solid var(--dm-border); border-radius: var(--dm-radius-md); }
154
+ .ct-add-group { display: flex; gap: .5rem; }
155
+ .ct-add-group .form-input { flex: 1; }
156
+ </style>
@@ -0,0 +1,212 @@
1
+ <div class="page-header">
2
+ <h2><span data-icon="menu"></span> <span id="ctx-title">New context menu</span></h2>
3
+ <div class="header-actions">
4
+ <a class="btn btn-secondary" href="#/context-menus"><span data-icon="arrow-left"></span> Back</a>
5
+ <button class="btn btn-primary" id="ctx-save"><span data-icon="check"></span> Save</button>
6
+ </div>
7
+ </div>
8
+
9
+ <div id="ctx-errors" class="alert alert-danger mb-4" style="display:none;"></div>
10
+
11
+ <p class="text-muted ctx-intro">
12
+ A context menu is not placed on a page the way a nav menu is. It attaches to a <strong>region</strong>
13
+ and opens when someone right-clicks inside it. So there are two questions to answer: which region, and
14
+ what the menu offers. Everything else has a sensible default.
15
+ </p>
16
+
17
+ <div class="row ctx-workspace">
18
+ <div class="col-8">
19
+ <div class="tabs" id="ctx-tabs">
20
+ <div class="tab-list">
21
+ <button class="tab-item active">Binding</button>
22
+ <button class="tab-item">Items</button>
23
+ <button class="tab-item">Appearance</button>
24
+ <button class="tab-item">Scope</button>
25
+ </div>
26
+
27
+ <!-- Binding ------------------------------------------------------- -->
28
+ <div class="tab-panel active">
29
+ <div class="row">
30
+ <div class="col-12">
31
+ <div class="card mb-4">
32
+ <div class="card-header"><h3 class="m-0">Where it binds</h3></div>
33
+ <div class="card-body">
34
+ <div class="form-group">
35
+ <label class="form-label" for="ctx-name">Name<button type="button" class="ctx-help" aria-label="What is this?" data-tooltip="Your label for this menu in the admin list. Visitors never see it.">?</button></label>
36
+ <input class="form-input" id="ctx-name" type="text" placeholder="Entry actions">
37
+ </div>
38
+ <div class="form-group">
39
+ <label class="form-label" for="ctx-slug">Slug<button type="button" class="ctx-help" aria-label="What is this?" data-tooltip="The filename this menu is saved as. Fixed once created, because the file is the record.">?</button></label>
40
+ <input class="form-input" id="ctx-slug" type="text" placeholder="entry-actions">
41
+ <small class="text-muted">Lowercase letters, numbers and hyphens. Cannot be changed later.</small>
42
+ </div>
43
+ <div class="form-group">
44
+ <label class="form-label" for="ctx-selector">Container selector<button type="button" class="ctx-help" aria-label="What is this?" data-tooltip="A CSS selector for the region, e.g. #product-grid, .entry-card or main. Right-clicks anywhere inside it are offered to this menu. body, html, * and :root are refused, because they would swallow every other menu on the page.">?</button></label>
45
+ <input class="form-input" id="ctx-selector" type="text" placeholder="#product-grid">
46
+ <small class="text-muted">The region this menu covers. Right-clicks inside it are offered to this menu.</small>
47
+ </div>
48
+ <div class="form-group">
49
+ <label class="form-label" for="ctx-match">Match (optional)<button type="button" class="ctx-help" aria-label="What is this?" data-tooltip="Narrows it: only claim the click when it landed on something matching this, e.g. [data-entry-id] to answer on rows but not the space between them. Blank means the whole container.">?</button></label>
50
+ <input class="form-input" id="ctx-match" type="text" placeholder="[data-entry-id]">
51
+ <small class="text-muted">Only claim right-clicks on descendants matching this. Leave empty to cover the whole container.</small>
52
+ </div>
53
+ <div class="form-group">
54
+ <label class="form-label" for="ctx-exclude">Exclude (optional)<button type="button" class="ctx-help" aria-label="What is this?" data-tooltip="Holes in the region. A right-click on one of these falls through to whatever encloses it, which is useful for links and inputs where the browser's own menu is more use.">?</button></label>
55
+ <input class="form-input" id="ctx-exclude" type="text" placeholder="a, button, input">
56
+ <small class="text-muted">Regions that decline, so the click falls through to whatever encloses them.</small>
57
+ </div>
58
+ </div>
59
+ </div>
60
+ </div>
61
+
62
+ <div class="col-12">
63
+ <div class="card mb-4">
64
+ <div class="card-header"><h3 class="m-0">Behaviour<button type="button" class="ctx-help" aria-label="What is this?" data-tooltip="Menus can sit inside one another. A right-click picks the INNERMOST menu covering the spot clicked, never the one that happened to load first.">?</button></h3></div>
65
+ <div class="card-body">
66
+ <div class="form-group">
67
+ <label class="form-label" for="ctx-inherit">When nested inside another menu<button type="button" class="ctx-help" aria-label="What is this?" data-tooltip="Append puts the outer menu's items after yours, Prepend before, and Stand alone shows only yours.">?</button></label>
68
+ <select class="form-select" id="ctx-inherit">
69
+ <option value="append">Show my items, then theirs (default)</option>
70
+ <option value="prepend">Show their items, then mine</option>
71
+ <option value="false">Show only my items</option>
72
+ </select>
73
+ </div>
74
+ <div class="form-group">
75
+ <label class="form-check">
76
+ <input type="checkbox" id="ctx-enabled" checked> Enabled
77
+ </label>
78
+ <small class="text-muted">Turn this off to park a menu without deleting it. A disabled menu is never sent to the browser.</small>
79
+ </div>
80
+ </div>
81
+ </div>
82
+ </div>
83
+ </div>
84
+ </div>
85
+
86
+ <!-- Items --------------------------------------------------------- -->
87
+ <div class="tab-panel">
88
+ <div class="card">
89
+ <div class="card-header">
90
+ <h3 class="m-0">Items<button type="button" class="ctx-help" aria-label="What is this?" data-tooltip="What the menu offers when it opens, top to bottom. An Item does something when clicked, a Divider draws a line, a Heading labels a group. A menu whose items are all dividers and headings is refused on save, because there would be nothing to click.">?</button></h3>
91
+ <button class="btn btn-sm btn-secondary" id="ctx-add-item"><span data-icon="plus"></span> Add item</button>
92
+ </div>
93
+ <div class="card-body">
94
+ <div id="ctx-items"></div>
95
+ <p class="text-muted mb-0" id="ctx-items-empty">No items yet. Every menu needs at least one.</p>
96
+ </div>
97
+ </div>
98
+ </div>
99
+
100
+ <!-- Appearance ---------------------------------------------------- -->
101
+ <div class="tab-panel">
102
+ <div class="card">
103
+ <div class="card-header"><h3 class="m-0">Appearance<button type="button" class="ctx-help" aria-label="What is this?" data-tooltip="How the panel looks when it opens. Leave anything blank to inherit the site theme, which is usually what you want so the menu matches everything around it.">?</button></h3></div>
104
+ <div class="card-body">
105
+ <div class="row">
106
+ <div class="col-6">
107
+ <div class="form-group">
108
+ <label class="form-label" for="ctx-accent">Accent<button type="button" class="ctx-help" aria-label="What is this?" data-tooltip="The colour used for hover and the active item.">?</button></label>
109
+ <select class="form-select" id="ctx-accent">
110
+ <option value="">Theme default</option>
111
+ <option value="primary">Primary</option>
112
+ <option value="success">Success</option>
113
+ <option value="danger">Danger</option>
114
+ <option value="warning">Warning</option>
115
+ <option value="info">Info</option>
116
+ </select>
117
+ <small class="text-muted">Tints the border and hover wash. Labels keep the theme's text colour so they stay readable.</small>
118
+ </div>
119
+ <div class="form-group">
120
+ <label class="form-label" for="ctx-transition">Transition</label>
121
+ <select class="form-select" id="ctx-transition">
122
+ <option value="">Default (scale)</option>
123
+ <option value="fade">Fade</option>
124
+ <option value="slide">Slide</option>
125
+ <option value="none">None</option>
126
+ </select>
127
+ </div>
128
+ <div class="form-group">
129
+ <label class="form-label" for="ctx-density">Density</label>
130
+ <select class="form-select" id="ctx-density">
131
+ <option value="">Comfortable</option>
132
+ <option value="compact">Compact</option>
133
+ </select>
134
+ </div>
135
+ </div>
136
+ <div class="col-6">
137
+ <div class="form-group">
138
+ <label class="form-label" for="ctx-radius">Corner radius</label>
139
+ <select class="form-select" id="ctx-radius">
140
+ <option value="">Default</option>
141
+ <option value="none">Square</option>
142
+ <option value="sm">Small</option>
143
+ <option value="md">Medium</option>
144
+ <option value="lg">Large</option>
145
+ <option value="xl">Extra large</option>
146
+ </select>
147
+ </div>
148
+ <div class="form-group">
149
+ <label class="form-label" for="ctx-shadow">Shadow</label>
150
+ <select class="form-select" id="ctx-shadow">
151
+ <option value="">Default</option>
152
+ <option value="none">None</option>
153
+ <option value="sm">Small</option>
154
+ <option value="md">Medium</option>
155
+ <option value="lg">Large</option>
156
+ <option value="xl">Extra large</option>
157
+ </select>
158
+ </div>
159
+ <div class="form-group">
160
+ <label class="form-label" for="ctx-opacity">Opacity<button type="button" class="ctx-help" aria-label="What is this?" data-tooltip="Below 100 the panel lets the page show through. Anything under about 80 gets hard to read over busy content.">?</button></label>
161
+ <input class="form-input" id="ctx-opacity" type="number" min="20" max="100" placeholder="100">
162
+ <small class="text-muted">Below 100 gives a translucent panel with a blurred backdrop. Text stays fully opaque.</small>
163
+ </div>
164
+ </div>
165
+ </div>
166
+ </div>
167
+ </div>
168
+ </div>
169
+
170
+ <!-- Scope --------------------------------------------------------- -->
171
+ <div class="tab-panel">
172
+ <div class="card">
173
+ <div class="card-header"><h3 class="m-0">Scope<button type="button" class="ctx-help" aria-label="What is this?" data-tooltip="Which pages carry this menu. Leave BOTH blank and it applies site-wide. A menu that does not apply to a page is not sent to that page at all.">?</button></h3></div>
174
+ <div class="card-body">
175
+ <div class="row">
176
+ <div class="col-6">
177
+ <div class="form-group">
178
+ <label class="form-label" for="ctx-pages">Pages<button type="button" class="ctx-help" aria-label="What is this?" data-tooltip="Comma separated URL paths. Supports * as a wildcard, so /shop/* covers everything under /shop. Blank means every page.">?</button></label>
179
+ <input class="form-input" id="ctx-pages" type="text" placeholder="/shop/*, /about">
180
+ <small class="text-muted">Comma separated. Supports a trailing <code>/*</code>. Leave empty for site-wide.</small>
181
+ </div>
182
+ </div>
183
+ <div class="col-6">
184
+ <div class="form-group">
185
+ <label class="form-label" for="ctx-projects">Projects<button type="button" class="ctx-help" aria-label="What is this?" data-tooltip="Comma separated project slugs. The menu then applies only to pages belonging to those projects.">?</button></label>
186
+ <input class="form-input" id="ctx-projects" type="text" placeholder="core, shop">
187
+ <small class="text-muted">Comma separated project slugs.</small>
188
+ </div>
189
+ </div>
190
+ </div>
191
+ </div>
192
+ </div>
193
+ </div>
194
+ </div>
195
+
196
+ </div><!-- /col-8 -->
197
+
198
+ <!-- One preview, beside the tabs rather than inside them: four copies would
199
+ be four elements sharing #ctx-preview, and the menu binds by selector.
200
+ Sticky, so it stays put while a long Items tab scrolls past it. -->
201
+ <div class="col-4">
202
+ <div class="card ctx-preview-card">
203
+ <div class="card-header"><h3 class="m-0">Try it<button type="button" class="ctx-help" aria-label="What is this?" data-tooltip="The real menu, built from what you have entered, updating as you type. This is the component itself rather than a drawing of it.">?</button></h3></div>
204
+ <div class="card-body">
205
+ <p class="text-muted">Right-click the panel below to see this menu as configured.</p>
206
+ <div id="ctx-preview" class="p-4 text-center" style="border:1px dashed var(--dm-border); border-radius:var(--dm-radius-md);">
207
+ Right-click to preview
208
+ </div>
209
+ </div>
210
+ </div>
211
+ </div>
212
+ </div><!-- /ctx-workspace -->
@@ -0,0 +1,16 @@
1
+ <div class="page-header">
2
+ <h2><span data-icon="menu"></span> Context Menus</h2>
3
+ <div class="header-actions">
4
+ <a class="btn btn-primary" href="#/context-menus/new"><span data-icon="plus"></span> New context menu</a>
5
+ </div>
6
+ </div>
7
+ <p class="text-muted mb-4">
8
+ Right-click menus bound to a region of a page. Unlike navigation menus these have no slot -
9
+ they bind to a CSS selector and are resolved in the browser by whichever menu is nearest the click.
10
+ </p>
11
+
12
+ <div class="card">
13
+ <div class="card-body">
14
+ <div id="context-menus-table"></div>
15
+ </div>
16
+ </div>