domma-cms 0.75.0 → 0.77.0

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 (150) hide show
  1. package/CLAUDE.md +1 -1
  2. package/admin/js/api.js +1 -1
  3. package/admin/js/app.js +4 -4
  4. package/admin/js/lib/action-editor-arrange.js +1 -0
  5. package/admin/js/lib/api-endpoint-editor-arrange.js +2 -0
  6. package/admin/js/lib/api-tokens-arrange.js +3 -0
  7. package/admin/js/lib/block-editor-arrange.js +2 -0
  8. package/admin/js/lib/collection-editor-arrange.js +3 -0
  9. package/admin/js/lib/collection-entries-arrange.js +1 -0
  10. package/admin/js/lib/component-editor-arrange.js +27 -0
  11. package/admin/js/lib/context-menu-editor-arrange.js +1 -0
  12. package/admin/js/lib/dashboard-arrange.js +1 -0
  13. package/admin/js/lib/effects-arrange.js +3 -0
  14. package/admin/js/lib/form-editor-arrange.js +1 -0
  15. package/admin/js/lib/form-submissions-arrange.js +2 -0
  16. package/admin/js/lib/layouts-arrange.js +1 -0
  17. package/admin/js/lib/menu-editor-arrange.js +2 -0
  18. package/admin/js/lib/my-profile-arrange.js +1 -0
  19. package/admin/js/lib/notifications-arrange.js +1 -0
  20. package/admin/js/lib/page-editor-arrange.js +1 -0
  21. package/admin/js/lib/plugin-code-arrange.js +1 -0
  22. package/admin/js/lib/plugins-arrange.js +2 -0
  23. package/admin/js/lib/project-page-arrange.js +1 -0
  24. package/admin/js/lib/role-editor-arrange.js +3 -0
  25. package/admin/js/lib/roles-arrange.js +1 -0
  26. package/admin/js/lib/search-settings-arrange.js +1 -0
  27. package/admin/js/lib/sidebar-renderer.js +2 -2
  28. package/admin/js/lib/site-settings-arrange.js +1 -0
  29. package/admin/js/lib/user-editor-arrange.js +1 -0
  30. package/admin/js/lib/users-arrange.js +1 -0
  31. package/admin/js/lib/view-editor-arrange.js +14 -0
  32. package/admin/js/templates/action-editor.html +223 -147
  33. package/admin/js/templates/api-endpoint-editor.html +172 -117
  34. package/admin/js/templates/api-tokens.html +83 -10
  35. package/admin/js/templates/block-editor.html +194 -203
  36. package/admin/js/templates/collection-editor.html +191 -113
  37. package/admin/js/templates/collection-entries.html +99 -36
  38. package/admin/js/templates/component-editor.html +128 -126
  39. package/admin/js/templates/context-menu-editor.html +208 -148
  40. package/admin/js/templates/dashboard.html +184 -22
  41. package/admin/js/templates/effects.html +155 -72
  42. package/admin/js/templates/form-editor.html +41 -26
  43. package/admin/js/templates/form-submissions.html +78 -26
  44. package/admin/js/templates/layouts.html +126 -43
  45. package/admin/js/templates/menu-editor.html +57 -17
  46. package/admin/js/templates/menu-locations.html +54 -11
  47. package/admin/js/templates/my-profile.html +100 -12
  48. package/admin/js/templates/notifications.html +70 -10
  49. package/admin/js/templates/page-editor.html +47 -26
  50. package/admin/js/templates/plugin-code.html +91 -73
  51. package/admin/js/templates/plugins.html +100 -28
  52. package/admin/js/templates/project-detail.html +83 -145
  53. package/admin/js/templates/project-settings.html +88 -53
  54. package/admin/js/templates/role-editor.html +158 -83
  55. package/admin/js/templates/roles.html +78 -7
  56. package/admin/js/templates/search.html +193 -216
  57. package/admin/js/templates/settings.html +404 -472
  58. package/admin/js/templates/user-editor.html +150 -14
  59. package/admin/js/templates/users.html +69 -9
  60. package/admin/js/templates/view-editor.html +203 -180
  61. package/admin/js/templates/view-preview.html +45 -45
  62. package/admin/js/views/action-editor.js +7 -1
  63. package/admin/js/views/api-endpoint-editor.js +2 -3
  64. package/admin/js/views/api-tokens.js +62 -8
  65. package/admin/js/views/block-editor.js +14 -8
  66. package/admin/js/views/collection-editor.js +64 -5
  67. package/admin/js/views/collection-entries.js +50 -1
  68. package/admin/js/views/component-editor.js +2 -28
  69. package/admin/js/views/context-menu-editor.js +54 -55
  70. package/admin/js/views/dashboard.js +7 -1
  71. package/admin/js/views/effects.js +31 -1
  72. package/admin/js/views/form-editor.js +7 -7
  73. package/admin/js/views/form-submissions.js +11 -1
  74. package/admin/js/views/index.js +1 -1
  75. package/admin/js/views/layouts.js +81 -1
  76. package/admin/js/views/login.js +2 -2
  77. package/admin/js/views/menu-editor.js +21 -43
  78. package/admin/js/views/menu-locations.js +16 -1
  79. package/admin/js/views/my-profile.js +1 -1
  80. package/admin/js/views/notifications.js +18 -1
  81. package/admin/js/views/page-editor.js +26 -26
  82. package/admin/js/views/plugin-code.js +23 -4
  83. package/admin/js/views/plugins.js +97 -39
  84. package/admin/js/views/project-detail.js +9 -1
  85. package/admin/js/views/project-settings.js +1 -1
  86. package/admin/js/views/role-editor.js +11 -3
  87. package/admin/js/views/roles.js +49 -4
  88. package/admin/js/views/search.js +7 -1
  89. package/admin/js/views/settings.js +13 -3
  90. package/admin/js/views/user-editor.js +1 -1
  91. package/admin/js/views/users.js +13 -4
  92. package/admin/js/views/view-editor.js +1 -1
  93. package/admin/js/views/view-preview.js +1 -1
  94. package/package.json +1 -1
  95. package/plugins/_lib/admin/mail/compose-window.js +97 -40
  96. package/plugins/_lib/admin/mail/contacts.js +4 -1
  97. package/plugins/_lib/admin/mail/diagnostics-section.js +4 -1
  98. package/plugins/_lib/admin/mail/folder-tree.js +1 -1
  99. package/plugins/_lib/admin/mail/image-senders-section.js +69 -90
  100. package/plugins/_lib/admin/mail/mail.css +1 -1
  101. package/plugins/_lib/admin/mail/mail.html +1 -1
  102. package/plugins/_lib/admin/mail/reader-view.js +303 -44
  103. package/plugins/_lib/admin/mail/when-closed.js +26 -0
  104. package/plugins/_lib/mail/readRoutes.js +43 -1
  105. package/plugins/_lib/mail/unreadBadge.js +42 -0
  106. package/plugins/free-tier.lock.json +5 -5
  107. package/plugins/mail-reader/CLAUDE.md +1 -0
  108. package/plugins/mail-reader/plugin.js +3 -1
  109. package/plugins/mail-reader/plugin.json +5 -4
  110. package/server/routes/api/api-tokens.js +56 -0
  111. package/server/routes/api/auth.js +7 -1
  112. package/server/routes/api/collections.js +13 -1
  113. package/server/routes/api/dashboard.js +32 -4
  114. package/server/routes/api/effects.js +68 -13
  115. package/server/routes/api/forms.js +104 -13
  116. package/server/routes/api/layouts.js +93 -2
  117. package/server/routes/api/notifications.js +140 -14
  118. package/server/routes/api/plugins.js +135 -4
  119. package/server/routes/api/roles.js +111 -0
  120. package/server/routes/api/search.js +38 -4
  121. package/server/routes/api/settings.js +52 -0
  122. package/server/routes/api/users.js +65 -3
  123. package/server/routes/api/views.js +55 -1
  124. package/server/server.js +12 -0
  125. package/server/services/components.js +9 -0
  126. package/server/services/plugins.js +7 -0
  127. package/server/services/roles.js +3 -0
  128. package/server/services/search.js +58 -0
  129. package/server/services/users.js +16 -2
  130. package/server/services/views.js +14 -0
  131. package/admin/js/templates/dashboard/activity-feed.html +0 -3
  132. package/admin/js/templates/dashboard/cache.html +0 -32
  133. package/admin/js/templates/dashboard/health-detail.html +0 -2
  134. package/admin/js/templates/dashboard/journeys.html +0 -17
  135. package/admin/js/templates/dashboard/kpi-strip.html +0 -34
  136. package/admin/js/templates/dashboard/spike-feed.html +0 -3
  137. package/admin/js/templates/dashboard/top-pages.html +0 -3
  138. package/admin/js/templates/dashboard/traffic-chart.html +0 -3
  139. package/admin/js/templates/plugin-marketplace.html +0 -29
  140. package/admin/js/views/block-editor-enhance.js +0 -1
  141. package/admin/js/views/dashboard/lib/escape.js +0 -1
  142. package/admin/js/views/dashboard/widgets/activity-feed.js +0 -1
  143. package/admin/js/views/dashboard/widgets/cache.js +0 -1
  144. package/admin/js/views/dashboard/widgets/health-detail.js +0 -1
  145. package/admin/js/views/dashboard/widgets/journeys.js +0 -1
  146. package/admin/js/views/dashboard/widgets/kpi-strip.js +0 -1
  147. package/admin/js/views/dashboard/widgets/spike-feed.js +0 -6
  148. package/admin/js/views/dashboard/widgets/top-pages.js +0 -1
  149. package/admin/js/views/dashboard/widgets/traffic-chart.js +0 -1
  150. package/admin/js/views/plugin-marketplace.js +0 -1
@@ -1,32 +1,194 @@
1
- <div class="view-header">
2
- <h1><span data-icon="home"></span> Dashboard</h1>
3
- <div class="view-header-actions">
4
- <span class="dash-updated" id="dash-updated"></span>
5
- <a href="#/pages/new" class="btn btn-primary"><span data-icon="plus"></span> New Page</a>
1
+ <!-- The core Dashboard. Bound with M.applyBindings - see admin/views/dashboard.js, which also inserts the
2
+ row templates (the router renders a templateUrl through _.render, eating any {{…}}). No page heading:
3
+ the Tool banner above is the heading; Refresh and New page are banner actions. -->
4
+ <div class="dbx">
5
+ <div class="dbx-top">
6
+ <small class="dbx-asof" data-bind-text="asOf.value"></small>
7
+ <span data-help-title="Dashboard" data-help="What the site is doing and what needs you, at a glance. Traffic comes from the site's own analytics (no cookies, no third party). The numbers refresh every 30 seconds while this screen is open; Refresh in the banner reloads everything."></span>
6
8
  </div>
7
- </div>
9
+ <p class="dbx-note is-warn" data-bind-hidden="!warning.value" data-bind-text="warning.value"></p>
8
10
 
9
- <div id="dash-warnings" class="dash-warnings" hidden></div>
11
+ <div class="dbx-kpis">
12
+ <div class="dbx-card dbx-kpi">
13
+ <span class="dbx-kpi-icon" aria-hidden="true"><span data-icon="trending-up" data-icon-size="18"></span></span>
14
+ <div class="dbx-kpi-body">
15
+ <strong class="dbx-kpi-value" data-bind-text="kpi.value.hits">—</strong>
16
+ <span class="dbx-kpi-label">Page views today
17
+ <span data-help="Every page view recorded today (UTC), compared with the whole of yesterday."></span></span>
18
+ <small class="dbx-delta" data-bind-hidden="!kpi.value.hitsDelta" data-bind-text="kpi.value.hitsDelta" data-bind-class="kpi.value.hitsUp ? 'is-up' : 'is-down'"></small>
19
+ </div>
20
+ </div>
21
+ <div class="dbx-card dbx-kpi">
22
+ <span class="dbx-kpi-icon" aria-hidden="true"><span data-icon="users" data-icon-size="18"></span></span>
23
+ <div class="dbx-kpi-body">
24
+ <strong class="dbx-kpi-value" data-bind-text="kpi.value.active">—</strong>
25
+ <span class="dbx-kpi-label">On the site now
26
+ <span data-help="Visitors seen in the last 5 minutes."></span></span>
27
+ </div>
28
+ </div>
29
+ <a class="dbx-card dbx-kpi is-link" href="#/pages">
30
+ <span class="dbx-kpi-icon" aria-hidden="true"><span data-icon="file-text" data-icon-size="18"></span></span>
31
+ <div class="dbx-kpi-body">
32
+ <strong class="dbx-kpi-value" data-bind-text="kpi.value.pages">—</strong>
33
+ <span class="dbx-kpi-label">Pages</span>
34
+ <small class="dbx-kpi-sub" data-bind-text="kpi.value.pagesSub"></small>
35
+ </div>
36
+ </a>
37
+ <a class="dbx-card dbx-kpi is-link" href="#/forms">
38
+ <span class="dbx-kpi-icon" aria-hidden="true"><span data-icon="inbox" data-icon-size="18"></span></span>
39
+ <div class="dbx-kpi-body">
40
+ <strong class="dbx-kpi-value" data-bind-text="kpi.value.submissions">—</strong>
41
+ <span class="dbx-kpi-label">Form submissions this week
42
+ <span data-help="Submissions to the site's forms in the last 7 days, counted where each form's submissions are kept."></span></span>
43
+ <small class="dbx-kpi-sub" data-bind-text="kpi.value.submissionsSub"></small>
44
+ </div>
45
+ </a>
46
+ <div class="dbx-card dbx-kpi">
47
+ <span class="dbx-kpi-icon" aria-hidden="true"><span data-icon="activity" data-icon-size="18"></span></span>
48
+ <div class="dbx-kpi-body">
49
+ <strong class="dbx-kpi-value"><span class="dbx-chip" data-bind-class="'is-' + kpi.value.healthTone" data-bind-text="kpi.value.health">—</span></strong>
50
+ <span class="dbx-kpi-label">Site health
51
+ <span data-help="The server's own checks - disk, storage, plugins, the database when one is used. Anything that is not OK is listed under Needs attention."></span></span>
52
+ </div>
53
+ </div>
54
+ </div>
10
55
 
11
- <div class="dashboard-grid">
12
- <section id="dash-kpi-strip" class="dash-row dash-row-kpi"></section>
56
+ <div class="dbx-split">
57
+ <section class="dbx-card">
58
+ <div class="dbx-card-title">Needs attention
59
+ <span data-help-title="Needs attention" data-help="What the rest of the admin is flagging: the red and amber badges from the sidebar (a missing layout, an expired API token, a plugin that is not running, settings quietly wrong…), unread notifications, and any health check that is not OK. Click one to go there."></span>
60
+ <span class="dbx-count" data-bind-text="attention.value.length"></span></div>
61
+ <p class="dbx-note is-ok" data-bind-hidden="!loaded.value || attention.value.length">
62
+ <span data-icon="check-circle" data-icon-size="15"></span> Nothing needs you right now.</p>
63
+ <div class="dbx-list dbx-attention" data-each="attention.value key=id"></div>
64
+ </section>
65
+ <section class="dbx-card">
66
+ <div class="dbx-card-title">Recent activity
67
+ <span data-help="Pages edited or published, and new entries in the site's collections - newest first. Click one to open it."></span></div>
68
+ <p class="dbx-note" data-bind-hidden="!loaded.value || activity.value.length">Nothing yet.</p>
69
+ <div class="dbx-list dbx-activity" data-each="activity.value key=id"></div>
70
+ </section>
71
+ </div>
13
72
 
14
- <section class="dash-row dash-row-traffic">
15
- <div id="dash-traffic-chart" class="dash-card dash-card-wide"></div>
16
- <div id="dash-spike-feed" class="dash-card"></div>
73
+ <section class="dbx-card">
74
+ <div class="dbx-card-title">Traffic - the last 30 days
75
+ <span data-help="Page views per day, across the whole site."></span></div>
76
+ <div class="dbx-chart" data-bind-hidden="!hasChart.value"><canvas class="dbx-canvas"></canvas></div>
77
+ <p class="dbx-note" data-bind-hidden="hasChart.value" data-bind-text="chartNote.value"></p>
17
78
  </section>
18
79
 
19
- <section class="dash-row dash-row-content">
20
- <div id="dash-top-pages" class="dash-card"></div>
21
- <div id="dash-journeys" class="dash-card"></div>
22
- </section>
80
+ <div class="dbx-split">
81
+ <section class="dbx-card">
82
+ <div class="dbx-card-title">Top pages - the last 7 days
83
+ <span data-help="The most-viewed addresses this week, with the change on the week before. Right-click (or ⋮) one to open it, copy its address or edit the page."></span></div>
84
+ <p class="dbx-note" data-bind-hidden="!loaded.value || topPages.value.length">No page views recorded yet.</p>
85
+ <div class="dbx-list dbx-top-pages" data-each="topPages.value key=id"></div>
86
+ </section>
87
+ <section class="dbx-card">
88
+ <div class="dbx-card-title">Spikes - the last hour
89
+ <span data-help="Addresses getting far more views than usual this hour - a link shared somewhere, or a bot. The number beside each is how many times its normal rate."></span></div>
90
+ <p class="dbx-note" data-bind-hidden="!loaded.value || spikes.value.length">Nothing unusual.</p>
91
+ <div class="dbx-list dbx-spikes" data-each="spikes.value key=id"></div>
92
+ </section>
93
+ </div>
23
94
 
24
- <section class="dash-row dash-row-health">
25
- <div id="dash-health-detail" class="dash-card"></div>
26
- <div id="dash-activity-feed" class="dash-card"></div>
95
+ <section class="dbx-card">
96
+ <div class="dbx-card-title">Visits - the last 7 days
97
+ <span data-help-title="Visits" data-help="Where visits start and end, the commonest moves between pages, and the bounce rate - visits that viewed a single page."></span>
98
+ <span class="dbx-spacer"></span>
99
+ <small class="dbx-muted" data-bind-hidden="!bounce.value" data-bind-text="bounce.value"></small></div>
100
+ <p class="dbx-note" data-bind-hidden="!loaded.value || hasJourneys.value">No visits recorded yet.</p>
101
+ <div class="dbx-journeys" data-bind-hidden="!hasJourneys.value">
102
+ <div><h4>Arrived at</h4><div class="dbx-list dbx-entry" data-each="entry.value key=id"></div></div>
103
+ <div><h4>Left from</h4><div class="dbx-list dbx-exit" data-each="exit.value key=id"></div></div>
104
+ <div><h4>Moved between</h4><div class="dbx-list dbx-paths" data-each="paths.value key=id"></div></div>
105
+ </div>
27
106
  </section>
28
107
 
29
- <section class="dash-row">
30
- <div id="dash-cache" class="dash-card dash-card-wide"></div>
31
- </section>
108
+ <div class="dbx-split">
109
+ <section class="dbx-card">
110
+ <div class="dbx-card-title">Health checks
111
+ <span data-help="Each check the server runs on itself, with what it found."></span></div>
112
+ <p class="dbx-note" data-bind-hidden="!loaded.value || checks.value.length">No checks reported.</p>
113
+ <div class="dbx-list dbx-checks" data-each="checks.value key=id"></div>
114
+ </section>
115
+ <section class="dbx-card" data-bind-hidden="!cache.value">
116
+ <div class="dbx-card-title">Response cache
117
+ <span data-help-title="Response cache" data-help="Pages are kept ready-rendered so repeat visits are fast; editing anything clears what it affects. Hit rate is how often a visit was served from it. Turning it off makes every visit render afresh."></span>
118
+ <span class="dbx-spacer"></span>
119
+ <label class="dbx-switch" title="Turn the cache on or off">
120
+ <input type="checkbox" class="form-switch" data-bind-checked="cacheOn.value" data-on-change="toggleCache">
121
+ <span data-bind-text="cacheOn.value ? 'On' : 'Off'"></span>
122
+ </label></div>
123
+ <div class="dbx-stats">
124
+ <div><small>Hit rate</small><strong data-bind-text="cacheView.value.hitRate"></strong><small data-bind-text="cacheView.value.hitsMisses"></small></div>
125
+ <div><small>Entries</small><strong data-bind-text="cacheView.value.size"></strong><small data-bind-text="cacheView.value.driver"></small></div>
126
+ <div><small>Writes</small><strong data-bind-text="cacheView.value.writes"></strong><small data-bind-text="cacheView.value.invalidations"></small></div>
127
+ <div><small>Last cleared</small><strong data-bind-text="cacheView.value.cleared"></strong></div>
128
+ </div>
129
+ <div class="dbx-actions">
130
+ <button type="button" class="btn btn-sm btn-ghost" data-on-click="clearCache"><span data-icon="trash" data-icon-size="13"></span> Clear the cache</button>
131
+ </div>
132
+ </section>
133
+ </div>
32
134
  </div>
135
+
136
+ <style>
137
+ .dbx { display: flex; flex-direction: column; gap: 1rem; }
138
+ .dbx [hidden] { display: none !important; }
139
+ .dbx-top { display: flex; align-items: center; gap: .4rem; justify-content: flex-end; }
140
+ .dbx-asof, .dbx-muted { color: var(--dm-text-muted); font-size: var(--dm-text-xs); }
141
+ .dbx-note { margin: 0; color: var(--dm-text-muted); font-size: var(--dm-text-sm); display: flex; align-items: center; gap: .35rem; }
142
+ .dbx-note.is-warn { padding: .55rem .8rem; border-radius: var(--dm-radius-md); background: color-mix(in srgb, var(--dm-warning) 14%, transparent); color: var(--dm-text); }
143
+ .dbx-note.is-ok { color: var(--dm-success); }
144
+ .dbx-card { border: 1px solid var(--dm-border); border-radius: var(--dm-radius-md); padding: .8rem .95rem; display: flex; flex-direction: column; gap: .6rem;
145
+ background: var(--dm-surface); min-width: 0; }
146
+ .dbx-card-title { display: flex; align-items: center; gap: .35rem; font-weight: 600; }
147
+ .dbx-card h4 { margin: 0 0 .35rem; font-size: var(--dm-text-sm); color: var(--dm-text-muted); font-weight: 600; }
148
+ .dbx-count { font-size: var(--dm-text-xs); color: var(--dm-text-muted); font-weight: 500; }
149
+ .dbx-spacer { flex: 1; }
150
+ .dbx-kpis { display: grid; grid-template-columns: repeat(auto-fit, minmax(10rem, 1fr)); gap: 1rem; }
151
+ .dbx-kpi { flex-direction: row; align-items: center; gap: .8rem; color: inherit; text-decoration: none; }
152
+ .dbx-kpi.is-link:hover, .dbx-kpi.is-link:focus-visible { border-color: var(--dm-primary); outline: none; }
153
+ .dbx-kpi-icon { display: inline-flex; align-items: center; justify-content: center; width: 2.4rem; height: 2.4rem; flex-shrink: 0;
154
+ border-radius: var(--dm-radius-md); color: var(--dm-primary); background: color-mix(in srgb, var(--dm-primary) 12%, transparent); }
155
+ .dbx-kpi-body { display: flex; flex-direction: column; min-width: 0; }
156
+ .dbx-kpi-value { font-size: var(--dm-text-2xl, 1.5rem); line-height: 1.2; font-variant-numeric: tabular-nums; }
157
+ .dbx-kpi-label { display: flex; align-items: center; gap: .3rem; color: var(--dm-text-muted); font-size: var(--dm-text-sm); }
158
+ .dbx-kpi-sub { color: var(--dm-text-muted); font-size: var(--dm-text-xs); }
159
+ .dbx-delta { font-size: var(--dm-text-xs); font-weight: 600; }
160
+ .dbx-delta.is-up { color: var(--dm-success); }
161
+ .dbx-delta.is-down { color: var(--dm-danger); }
162
+ .dbx-split { display: grid; grid-template-columns: repeat(auto-fit, minmax(22rem, 1fr)); gap: 1rem; }
163
+ .dbx-list { display: flex; flex-direction: column; }
164
+ .dbx-row { display: flex; align-items: center; gap: .6rem; padding: .45rem .3rem; border-bottom: 1px solid var(--dm-border); cursor: pointer; min-width: 0; }
165
+ .dbx-row:last-child { border-bottom: 0; }
166
+ .dbx-row:hover, .dbx-row:focus-visible { background: var(--dm-table-hover-bg, color-mix(in srgb, var(--dm-text) 6%, transparent)); outline: none; }
167
+ .dbx-row.is-static { cursor: default; }
168
+ .dbx-row.is-static:hover { background: none; }
169
+ .dbx-row.is-danger { box-shadow: inset 3px 0 0 var(--dm-danger); }
170
+ .dbx-row.is-warn { box-shadow: inset 3px 0 0 var(--dm-warning); }
171
+ .dbx-main { flex: 1; min-width: 0; display: flex; flex-direction: column; }
172
+ .dbx-main strong, .dbx-main small { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
173
+ .dbx-main strong { font-weight: 500; }
174
+ .dbx-main small { color: var(--dm-text-muted); font-size: var(--dm-text-xs); }
175
+ .dbx-value { font-variant-numeric: tabular-nums; font-weight: 600; flex-shrink: 0; }
176
+ .dbx-chip { display: inline-flex; align-items: center; font-size: var(--dm-text-xs); font-weight: 600; padding: .1rem .5rem; border-radius: 999px; white-space: nowrap;
177
+ background: color-mix(in srgb, var(--dm-text) 10%, transparent); color: var(--dm-text-muted); }
178
+ .dbx-chip.is-ok { background: color-mix(in srgb, var(--dm-success) 16%, transparent); color: var(--dm-success); }
179
+ .dbx-chip.is-warn { background: color-mix(in srgb, var(--dm-warning) 18%, transparent); color: var(--dm-warning); }
180
+ .dbx-chip.is-danger { background: color-mix(in srgb, var(--dm-danger) 16%, transparent); color: var(--dm-danger); }
181
+ .dbx-kpi-value .dbx-chip { font-size: var(--dm-text-sm); padding: .15rem .6rem; }
182
+ .dbx-kebab { border: 0; background: none; cursor: pointer; font-size: var(--dm-text-lg); color: var(--dm-text-muted); border-radius: var(--dm-radius-sm); padding: 0 .3rem; flex-shrink: 0; }
183
+ .dbx-kebab:hover { color: var(--dm-primary); }
184
+ .dbx-chart { height: 240px; position: relative; }
185
+ .dbx-journeys { display: grid; grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr)); gap: 1rem; }
186
+ .dbx-stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(7rem, 1fr)); gap: .8rem; }
187
+ .dbx-stats > div { display: flex; flex-direction: column; }
188
+ .dbx-stats small { color: var(--dm-text-muted); font-size: var(--dm-text-xs); }
189
+ .dbx-stats strong { font-size: var(--dm-text-lg); font-variant-numeric: tabular-nums; }
190
+ .dbx-switch { display: inline-flex; align-items: center; gap: .4rem; font-weight: 400; font-size: var(--dm-text-sm); cursor: pointer; }
191
+ .dbx-actions { display: flex; justify-content: flex-end; }
192
+ .dbx-actions .btn { display: inline-flex; align-items: center; gap: .3rem; }
193
+ @media (max-width: 640px) { .dbx-split { grid-template-columns: 1fr; } }
194
+ </style>
@@ -1,81 +1,104 @@
1
- <div class="view-header">
2
- <h1><span data-icon="sparkles"></span> Domma Effects</h1>
3
- <div>
4
- <button id="save-settings-btn" class="btn btn-primary">
5
- <span data-icon="save"></span> Save
6
- </button>
1
+ <!-- System › Effects. Bound with M.applyBindings - see admin/js/views/effects.js, which also inserts the
2
+ row template (the router renders a templateUrl through _.render, eating any {{…}}). No page heading:
3
+ the Tool banner above is the heading, and Save is a banner action. -->
4
+ <div class="fxx">
5
+ <div class="fxx-card fxx-live">
6
+ <div class="fxx-card-title">Live now
7
+ <span data-help-title="Live now" data-help="What the published site uses at the moment - the saved settings, not what is typed below until you Save. Every [reveal] and row reveal without its own duration, animation or threshold uses these."></span></div>
8
+ <div class="fxx-chips">
9
+ <span class="fxx-chip" data-bind-class="saved.value.respectMotion ? 'is-ok' : 'is-warn'" data-bind-text="motionText.value"></span>
10
+ <span class="fxx-chip" data-bind-text="'Reveal: ' + saved.value.defaultAnimation"></span>
11
+ <span class="fxx-chip" data-bind-text="saved.value.defaultDuration + ' ms'"></span>
12
+ <span class="fxx-chip" data-bind-text="'Threshold ' + saved.value.defaultThreshold"></span>
13
+ <span class="fxx-chip is-muted" data-bind-hidden="!defaultsText.value" data-bind-text="defaultsText.value"></span>
14
+ </div>
7
15
  </div>
8
- </div>
9
16
 
10
- <!-- Top-level tabs -->
11
- <div id="effects-tabs" class="tabs">
12
- <div class="tab-list">
13
- <button class="tab-item active">Settings</button>
14
- <button class="tab-item">Shortcode Reference</button>
15
- </div>
16
- <div class="tab-content">
17
+ <p class="fxx-unsaved" data-bind-hidden="!dirty.value">
18
+ <span data-icon="alert-circle" data-icon-size="15"></span> Unsaved changes
19
+ <span class="fxx-spacer"></span>
20
+ <button type="button" class="btn btn-sm btn-ghost" data-on-click="discard">Discard</button>
21
+ <button type="button" class="btn btn-sm btn-primary" data-on-click="save" data-bind-disabled="cannotSave.value">Save</button>
22
+ </p>
23
+
24
+ <div class="tabs fxx-tabs">
25
+ <div class="tab-list">
26
+ <button type="button" class="tab-item active">Settings <span class="fxx-dot" data-bind-hidden="!dirty.value" title="Unsaved changes">•</span></button>
27
+ <button type="button" class="tab-item">Effects <span class="fxx-count" data-bind-text="effectCount"></span></button>
28
+ <button type="button" class="tab-item">Guide</button>
29
+ </div>
17
30
 
18
- <!-- Settings tab panel -->
31
+ <!-- Settings -->
19
32
  <div class="tab-panel active">
20
- <div class="card mb-4">
21
- <div class="card-body">
22
- <div class="row mb-3">
23
- <div class="col">
24
- <label class="form-check-label">
25
- <input id="field-respect-motion" type="checkbox">
26
- Respect <code>prefers-reduced-motion</code>
27
- </label>
28
- <span class="form-hint">When enabled, JS effects are skipped for users who prefer reduced motion. Content remains visible.</span>
29
- </div>
33
+ <div class="fxx-card">
34
+ <div class="fxx-card-title">Scroll reveal defaults
35
+ <span data-help-title="Defaults" data-help="Used by [reveal] and [row reveal] when the shortcode does not set its own. A shortcode's own attribute always wins. Ctrl+S saves."></span></div>
36
+ <div class="fxx-grid">
37
+ <div class="fxx-wide fxx-setting">
38
+ <label class="form-label" for="fx-motion">Respect reduced motion
39
+ <span data-help-title="prefers-reduced-motion" data-help="Visitors can ask their device for less motion. With this on, the JavaScript effects are skipped for them and the content simply shows - nothing is hidden. Turn it off only if an effect carries meaning that would otherwise be lost."></span></label>
40
+ <input id="fx-motion" type="checkbox" class="form-switch" data-model="respectMotion.value">
30
41
  </div>
31
- <div class="row mb-3">
32
- <div class="col-6">
33
- <label class="form-label">Default animation duration (ms)</label>
34
- <input id="field-default-duration" type="number" class="form-input" min="0" max="10000"
35
- placeholder="600">
36
- <span class="form-hint">Used by reveal when no <code>duration</code> attribute is specified.</span>
37
- </div>
38
- <div class="col-6">
39
- <label class="form-label">Default reveal animation</label>
40
- <select id="field-default-animation" class="form-select">
41
- <option value="fade">Fade</option>
42
- <option value="slide-up">Slide up</option>
43
- <option value="slide-down">Slide down</option>
44
- <option value="zoom">Zoom</option>
45
- <option value="flip">Flip</option>
46
- </select>
47
- </div>
42
+ <div>
43
+ <label class="form-label" for="fx-anim">Reveal animation</label>
44
+ <select id="fx-anim" class="form-input" data-model="defaultAnimation.value">
45
+ <option value="fade">Fade</option>
46
+ <option value="slide-up">Slide up</option>
47
+ <option value="slide-down">Slide down</option>
48
+ <option value="zoom">Zoom</option>
49
+ <option value="flip">Flip</option>
50
+ </select>
51
+ <small class="fxx-err" data-bind-hidden="!animErr.value" data-bind-text="animErr.value"></small>
48
52
  </div>
49
- <div class="row">
50
- <div class="col-6">
51
- <label class="form-label">Default scroll threshold</label>
52
- <input id="field-default-threshold" type="number" class="form-input" min="0" max="1"
53
- step="0.05"
54
- placeholder="0.1">
55
- <span class="form-hint">Fraction of element visible before reveal fires (0.0-1.0).</span>
56
- </div>
53
+ <div>
54
+ <label class="form-label" for="fx-duration">Duration (ms)
55
+ <span data-help="How long a reveal takes. 600 ms is a comfortable default; above about 1000 it starts to feel slow."></span></label>
56
+ <input id="fx-duration" type="number" class="form-input" min="1" max="10000" step="50" placeholder="600" data-model="defaultDuration.value">
57
+ <small class="fxx-err" data-bind-hidden="!durErr.value" data-bind-text="durErr.value"></small>
57
58
  </div>
59
+ <div>
60
+ <label class="form-label" for="fx-threshold">Threshold
61
+ <span data-help-title="Threshold" data-help="How much of an element must be on screen before it reveals: 0 = as soon as any of it shows, 1 = only once all of it shows. 0.1 suits most pages; tall blocks need a low value or they may never reveal on a small screen."></span></label>
62
+ <input id="fx-threshold" type="number" class="form-input" min="0" max="1" step="0.05" placeholder="0.1" data-model="defaultThreshold.value">
63
+ <small class="fxx-err" data-bind-hidden="!thrErr.value" data-bind-text="thrErr.value"></small>
64
+ </div>
65
+ </div>
66
+ <div class="fxx-actions">
67
+ <button type="button" class="btn btn-sm btn-ghost" data-on-click="resetDefaults" title="Put back Domma's defaults (then Save)">Domma's defaults</button>
68
+ <span class="fxx-spacer"></span>
69
+ <button type="button" class="btn btn-sm btn-primary" data-on-click="save" data-bind-disabled="cannotSave.value">
70
+ <span data-icon="save" data-icon-size="14"></span> Save</button>
58
71
  </div>
59
72
  </div>
60
73
  </div>
61
74
 
62
- <!-- Shortcode Reference tab panel -->
75
+ <!-- Effects: the catalogue the page editor inserts from -->
63
76
  <div class="tab-panel">
64
- <p class="text-muted mb-3">Use these shortcodes in any page's Markdown content. The <strong>Effects</strong>
65
- toolbar button in the editor inserts them automatically.</p>
66
-
67
- <!-- Sub-tabs -->
68
- <div class="mb-3" style="display:flex;gap:6px;flex-wrap:wrap;">
69
- <button class="btn btn-sm effects-tab-btn active" data-fx-tab="entrance">Entrance</button>
70
- <button class="btn btn-sm effects-tab-btn" data-fx-tab="animation">Animation</button>
71
- <button class="btn btn-sm effects-tab-btn" data-fx-tab="text">Text</button>
72
- <button class="btn btn-sm effects-tab-btn" data-fx-tab="visual">Visual</button>
73
- <button class="btn btn-sm effects-tab-btn" data-fx-tab="examples">Examples</button>
74
- <button class="btn btn-sm effects-tab-btn" data-fx-tab="celebrations">Celebrations</button>
77
+ <div class="fxx-card">
78
+ <div class="fxx-bar">
79
+ <div class="fxx-seg fxx-cats" role="group" aria-label="Category" data-each="cats.value key=key"></div>
80
+ <input type="search" class="form-input fxx-search" placeholder="Search effects or settings…" aria-label="Search effects" data-model="query.value">
81
+ <span data-help-title="Effects" data-help="Every effect the page editor's Effects button can insert. Click one for its settings and an example; right-click it (or ⋮, or Shift+F10) to copy its shortcode. An effect that works inside [card], [hero] and friends can also be switched on as an attribute of that shortcode."></span>
82
+ </div>
83
+ <p class="fxx-note" data-bind-hidden="rows.value.length">No effects match.</p>
84
+ <div class="fxx-list" data-each="rows.value key=name"></div>
85
+ </div>
75
86
  </div>
76
87
 
77
- <!-- Entrance tab -->
78
- <div id="tab-entrance" class="effects-tab-panel">
88
+ <!-- Guide: the long-form reference -->
89
+ <div class="tab-panel">
90
+ <div class="fxx-card">
91
+ <p class="fxx-note">Use these shortcodes in any page's Markdown. The <strong>Effects</strong> toolbar button in
92
+ the editor inserts them for you.</p>
93
+ <div class="fxx-seg fxx-guide-tabs" role="group" aria-label="Guide section">
94
+ <button type="button" data-on-click="showGuide('entrance')" data-bind-class="guide.value === 'entrance' && 'is-on'">Entrance</button>
95
+ <button type="button" data-on-click="showGuide('animation')" data-bind-class="guide.value === 'animation' && 'is-on'">Animation</button>
96
+ <button type="button" data-on-click="showGuide('text')" data-bind-class="guide.value === 'text' && 'is-on'">Text</button>
97
+ <button type="button" data-on-click="showGuide('visual')" data-bind-class="guide.value === 'visual' && 'is-on'">Visual</button>
98
+ <button type="button" data-on-click="showGuide('examples')" data-bind-class="guide.value === 'examples' && 'is-on'">Examples</button>
99
+ <button type="button" data-on-click="showGuide('celebrations')" data-bind-class="guide.value === 'celebrations' && 'is-on'">Celebrations</button>
100
+ </div>
101
+ <div id="tab-entrance" class="fxx-guide-panel" data-bind-hidden="guide.value !== 'entrance'">
79
102
  <h3 class="mb-2">Reveal</h3>
80
103
  <p class="text-muted mb-2">Triggers a scroll-activated entrance animation when the element enters the
81
104
  viewport.
@@ -198,7 +221,7 @@ Markdown **works** inside.
198
221
  </div>
199
222
 
200
223
  <!-- Animation tab -->
201
- <div id="tab-animation" class="effects-tab-panel">
224
+ <div id="tab-animation" class="fxx-guide-panel" data-bind-hidden="guide.value !== 'animation'">
202
225
  <h3 class="mb-2">Breathe</h3>
203
226
  <p class="text-muted mb-2">Continuous gentle scaling - makes elements feel alive. Ideal for hero images or
204
227
  call-to-action buttons.</p>
@@ -256,7 +279,7 @@ Content here.
256
279
  </div>
257
280
 
258
281
  <!-- Text tab -->
259
- <div id="tab-text" class="effects-tab-panel">
282
+ <div id="tab-text" class="fxx-guide-panel" data-bind-hidden="guide.value !== 'text'">
260
283
  <h3 class="mb-2">Typewriter (Scribe)</h3>
261
284
  <p class="text-muted mb-2">Types text character by character. Supports a simple one-shot mode or a full
262
285
  action script for sequenced multi-step animations.</p>
@@ -418,7 +441,7 @@ Scrambled reveal text.
418
441
  </div>
419
442
 
420
443
  <!-- Visual tab -->
421
- <div id="tab-visual" class="effects-tab-panel">
444
+ <div id="tab-visual" class="fxx-guide-panel" data-bind-hidden="guide.value !== 'visual'">
422
445
  <h3 class="mb-2">Ripple</h3>
423
446
  <p class="text-muted mb-2">Click-triggered ripple effect on the element. Good for interactive content
424
447
  blocks.</p>
@@ -466,7 +489,7 @@ Content on animated background.
466
489
  </div>
467
490
 
468
491
  <!-- Examples tab -->
469
- <div id="tab-examples" class="effects-tab-panel">
492
+ <div id="tab-examples" class="fxx-guide-panel" data-bind-hidden="guide.value !== 'examples'">
470
493
  <h3 class="mb-2">Hero section with typewriter</h3>
471
494
  <pre class="code-block mb-3">[reveal animation="fade"]
472
495
  # Welcome to our site
@@ -530,7 +553,7 @@ A compelling subheading that draws users in.
530
553
  </div>
531
554
 
532
555
  <!-- Celebrations tab -->
533
- <div id="tab-celebrations" class="effects-tab-panel">
556
+ <div id="tab-celebrations" class="fxx-guide-panel" data-bind-hidden="guide.value !== 'celebrations'">
534
557
  <h3 class="mb-2">Firework <span class="badge badge-info">CSS only</span></h3>
535
558
  <p class="text-muted mb-2">Pure CSS particle effect - no JavaScript required. Works even without the plugin
536
559
  enabled. Use self-closing for standalone particles, or wrapping to apply the firework classes to
@@ -747,6 +770,66 @@ The strips rain down inside this block only.
747
770
  full-page overlay (<code>position:fixed</code>); the wrapping form attaches the canvas to the
748
771
  wrapping div so the parade is constrained to that block.</p>
749
772
  </div>
750
- </div><!-- /tab-panel shortcodes -->
751
- </div><!-- /tab-content -->
752
- </div><!-- /effects-tabs -->
773
+ </div>
774
+ </div>
775
+ </div>
776
+ </div>
777
+
778
+ <style>
779
+ .fxx { display: flex; flex-direction: column; gap: .75rem; }
780
+ .fxx [hidden], .fxx-form [hidden] { display: none !important; }
781
+ .fxx-card { border: 1px solid var(--dm-border); border-radius: var(--dm-radius-md); padding: .75rem .9rem; display: flex; flex-direction: column; gap: .7rem; margin-top: .75rem; }
782
+ .fxx-live { margin-top: 0; }
783
+ .fxx-card-title { display: flex; align-items: center; gap: .35rem; font-weight: 600; }
784
+ .fxx-chips { display: flex; flex-wrap: wrap; gap: .35rem; }
785
+ .fxx-chip { display: inline-flex; align-items: center; font-size: var(--dm-text-xs); font-weight: 600; padding: .1rem .55rem; border-radius: 999px; white-space: nowrap;
786
+ background: color-mix(in srgb, var(--dm-text) 10%, transparent); color: var(--dm-text-muted); }
787
+ .fxx-chip.is-ok { background: color-mix(in srgb, var(--dm-success) 16%, transparent); color: var(--dm-success); }
788
+ .fxx-chip.is-warn { background: color-mix(in srgb, var(--dm-warning) 18%, transparent); color: var(--dm-warning); }
789
+ .fxx-chip.is-info { background: color-mix(in srgb, var(--dm-primary) 14%, transparent); color: var(--dm-primary); }
790
+ .fxx-chip.is-muted { background: none; font-weight: 400; }
791
+ .fxx-unsaved { margin: 0; display: flex; align-items: center; gap: .5rem; padding: .5rem .8rem; border-radius: var(--dm-radius-md);
792
+ background: color-mix(in srgb, var(--dm-warning) 16%, transparent); color: var(--dm-text); }
793
+ .fxx-dot { color: var(--dm-warning); margin-left: .2rem; }
794
+ .fxx-count { opacity: .65; font-size: var(--dm-text-xs); margin-left: .2rem; }
795
+ .fxx-spacer { flex: 1; }
796
+ .fxx-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: .85rem; }
797
+ .fxx-wide { grid-column: 1 / -1; }
798
+ .fxx-setting { display: flex; align-items: center; justify-content: space-between; gap: .75rem; }
799
+ .fxx-setting .form-label { margin: 0; }
800
+ .fxx .form-label { display: flex; align-items: center; gap: .3rem; }
801
+ .fxx-err { display: block; color: var(--dm-danger); font-size: var(--dm-text-xs); margin-top: .25rem; }
802
+ .fxx-actions { display: flex; align-items: center; gap: .4rem; padding-top: .6rem; border-top: 1px solid var(--dm-border); }
803
+ .fxx-actions .btn, .fxx-unsaved .btn { display: inline-flex; align-items: center; gap: .3rem; }
804
+ .fxx-note { margin: 0; color: var(--dm-text-muted); font-size: var(--dm-text-sm); }
805
+ .fxx-bar { display: flex; flex-wrap: wrap; gap: .5rem; align-items: center; }
806
+ .fxx-search { flex: 1; min-width: 10rem; max-width: 22rem; }
807
+ .fxx-seg { display: inline-flex; flex-wrap: wrap; border: 1px solid var(--dm-border); border-radius: var(--dm-radius-md); overflow: hidden; }
808
+ .fxx-seg button { border: 0; background: none; color: var(--dm-text); padding: .3rem .65rem; cursor: pointer; font: inherit; font-size: var(--dm-text-sm); }
809
+ .fxx-seg button + button { border-left: 1px solid var(--dm-border); }
810
+ .fxx-seg button b { font-weight: 500; opacity: .65; margin-left: .2rem; }
811
+ .fxx-seg button:hover { background: var(--dm-table-hover-bg, color-mix(in srgb, var(--dm-text) 6%, transparent)); }
812
+ .fxx-seg button.is-on { background: var(--dm-primary); color: var(--dm-primary-text); }
813
+ .fxx-list { display: flex; flex-direction: column; }
814
+ .fxx-row { display: flex; align-items: center; gap: .75rem; padding: .55rem .35rem; border-bottom: 1px solid var(--dm-border); cursor: pointer; }
815
+ .fxx-row:last-child { border-bottom: 0; }
816
+ .fxx-row:hover, .fxx-row:focus-visible { background: var(--dm-table-hover-bg, color-mix(in srgb, var(--dm-text) 6%, transparent)); outline: none; }
817
+ .fxx-tag { flex-shrink: 0; min-width: 7.5rem; font-family: var(--dm-font-mono, monospace); font-size: var(--dm-text-sm); color: var(--dm-primary); }
818
+ .fxx-main { flex: 1; min-width: 0; display: flex; flex-direction: column; }
819
+ .fxx-main strong, .fxx-main small { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
820
+ .fxx-main small { color: var(--dm-text-muted); font-size: var(--dm-text-xs); }
821
+ .fxx-kebab { border: 0; background: none; cursor: pointer; font-size: var(--dm-text-lg); color: var(--dm-text-muted); border-radius: var(--dm-radius-sm); padding: 0 .3rem; }
822
+ .fxx-kebab:hover { color: var(--dm-primary); }
823
+ .fxx-guide-tabs { align-self: flex-start; }
824
+ /* The details slideover */
825
+ .fxx-form { display: flex; flex-direction: column; gap: .85rem; }
826
+ .fxx-form pre { margin: 0; white-space: pre-wrap; word-break: break-word; font-family: var(--dm-font-mono, monospace); font-size: var(--dm-text-sm);
827
+ padding: .6rem .75rem; border-radius: var(--dm-radius-md); border: 1px solid var(--dm-border); background: var(--dm-surface-raised, color-mix(in srgb, var(--dm-text) 5%, transparent)); color: var(--dm-text); }
828
+ .fxx-sub { display: flex; align-items: center; gap: .4rem; font-weight: 600; }
829
+ .fxx-attrs { width: 100%; border-collapse: collapse; font-size: var(--dm-text-sm); }
830
+ .fxx-attrs th, .fxx-attrs td { text-align: left; padding: .35rem .4rem; border-bottom: 1px solid var(--dm-border); vertical-align: top; }
831
+ .fxx-attrs th { color: var(--dm-text-muted); font-weight: 500; font-size: var(--dm-text-xs); }
832
+ .fxx-attrs code { color: var(--dm-primary); }
833
+ .fxx-attrs small { display: block; color: var(--dm-text-muted); }
834
+ @media (max-width: 720px) { .fxx-grid { grid-template-columns: 1fr; } .fxx-tag { min-width: 0; } }
835
+ </style>