domma-cms 0.76.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 (88) hide show
  1. package/CLAUDE.md +1 -1
  2. package/admin/js/api.js +1 -1
  3. package/admin/js/app.js +2 -2
  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/block-editor-arrange.js +2 -0
  7. package/admin/js/lib/collection-editor-arrange.js +3 -0
  8. package/admin/js/lib/collection-entries-arrange.js +1 -0
  9. package/admin/js/lib/component-editor-arrange.js +27 -0
  10. package/admin/js/lib/context-menu-editor-arrange.js +1 -0
  11. package/admin/js/lib/dashboard-arrange.js +1 -0
  12. package/admin/js/lib/form-editor-arrange.js +1 -0
  13. package/admin/js/lib/form-submissions-arrange.js +2 -0
  14. package/admin/js/lib/menu-editor-arrange.js +2 -0
  15. package/admin/js/lib/page-editor-arrange.js +1 -0
  16. package/admin/js/lib/plugin-code-arrange.js +1 -0
  17. package/admin/js/lib/project-page-arrange.js +1 -0
  18. package/admin/js/lib/role-editor-arrange.js +3 -0
  19. package/admin/js/lib/user-editor-arrange.js +1 -0
  20. package/admin/js/lib/view-editor-arrange.js +14 -0
  21. package/admin/js/templates/action-editor.html +223 -147
  22. package/admin/js/templates/api-endpoint-editor.html +172 -117
  23. package/admin/js/templates/block-editor.html +194 -203
  24. package/admin/js/templates/collection-editor.html +191 -113
  25. package/admin/js/templates/collection-entries.html +99 -36
  26. package/admin/js/templates/component-editor.html +128 -126
  27. package/admin/js/templates/context-menu-editor.html +208 -148
  28. package/admin/js/templates/dashboard.html +184 -22
  29. package/admin/js/templates/form-editor.html +41 -26
  30. package/admin/js/templates/form-submissions.html +78 -26
  31. package/admin/js/templates/menu-editor.html +57 -17
  32. package/admin/js/templates/menu-locations.html +54 -11
  33. package/admin/js/templates/page-editor.html +47 -26
  34. package/admin/js/templates/plugin-code.html +91 -73
  35. package/admin/js/templates/project-detail.html +83 -145
  36. package/admin/js/templates/project-settings.html +88 -53
  37. package/admin/js/templates/role-editor.html +158 -83
  38. package/admin/js/templates/user-editor.html +150 -14
  39. package/admin/js/templates/view-editor.html +203 -180
  40. package/admin/js/templates/view-preview.html +45 -45
  41. package/admin/js/views/action-editor.js +7 -1
  42. package/admin/js/views/api-endpoint-editor.js +2 -3
  43. package/admin/js/views/block-editor.js +14 -8
  44. package/admin/js/views/collection-editor.js +64 -5
  45. package/admin/js/views/collection-entries.js +50 -1
  46. package/admin/js/views/component-editor.js +2 -28
  47. package/admin/js/views/context-menu-editor.js +54 -55
  48. package/admin/js/views/dashboard.js +7 -1
  49. package/admin/js/views/form-editor.js +7 -7
  50. package/admin/js/views/form-submissions.js +11 -1
  51. package/admin/js/views/index.js +1 -1
  52. package/admin/js/views/login.js +2 -2
  53. package/admin/js/views/menu-editor.js +21 -43
  54. package/admin/js/views/menu-locations.js +16 -1
  55. package/admin/js/views/page-editor.js +26 -26
  56. package/admin/js/views/plugin-code.js +23 -4
  57. package/admin/js/views/project-detail.js +9 -1
  58. package/admin/js/views/project-settings.js +1 -1
  59. package/admin/js/views/role-editor.js +11 -3
  60. package/admin/js/views/user-editor.js +1 -1
  61. package/admin/js/views/view-editor.js +1 -1
  62. package/admin/js/views/view-preview.js +1 -1
  63. package/package.json +1 -1
  64. package/server/routes/api/dashboard.js +32 -4
  65. package/server/routes/api/forms.js +104 -13
  66. package/server/routes/api/users.js +12 -2
  67. package/server/routes/api/views.js +55 -1
  68. package/server/services/components.js +9 -0
  69. package/server/services/users.js +2 -2
  70. package/server/services/views.js +14 -0
  71. package/admin/js/templates/dashboard/activity-feed.html +0 -3
  72. package/admin/js/templates/dashboard/cache.html +0 -32
  73. package/admin/js/templates/dashboard/health-detail.html +0 -2
  74. package/admin/js/templates/dashboard/journeys.html +0 -17
  75. package/admin/js/templates/dashboard/kpi-strip.html +0 -34
  76. package/admin/js/templates/dashboard/spike-feed.html +0 -3
  77. package/admin/js/templates/dashboard/top-pages.html +0 -3
  78. package/admin/js/templates/dashboard/traffic-chart.html +0 -3
  79. package/admin/js/views/block-editor-enhance.js +0 -1
  80. package/admin/js/views/dashboard/lib/escape.js +0 -1
  81. package/admin/js/views/dashboard/widgets/activity-feed.js +0 -1
  82. package/admin/js/views/dashboard/widgets/cache.js +0 -1
  83. package/admin/js/views/dashboard/widgets/health-detail.js +0 -1
  84. package/admin/js/views/dashboard/widgets/journeys.js +0 -1
  85. package/admin/js/views/dashboard/widgets/kpi-strip.js +0 -1
  86. package/admin/js/views/dashboard/widgets/spike-feed.js +0 -6
  87. package/admin/js/views/dashboard/widgets/top-pages.js +0 -1
  88. package/admin/js/views/dashboard/widgets/traffic-chart.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,13 +1,12 @@
1
- <div class="view-header">
2
- <h1 id="editor-title"><span data-icon="edit-3"></span> Form Editor</h1>
3
- <div style="display:flex;gap:.5rem;align-items:center;">
4
- <a href="#/forms" class="btn btn-ghost btn-sm">
5
- <span data-icon="arrow-left"></span> All Forms
6
- </a>
7
- <button id="save-form-btn" class="btn btn-primary">
8
- <span data-icon="save"></span> Save
9
- </button>
10
- </div>
1
+ <!-- Form editor. No page heading: the Tool banner ("Forms › <title>") is the heading, and Save / All forms /
2
+ Submissions are banner actions. The bar below is bound with M.applyBindings (admin/js/views/form-editor.js). -->
3
+ <div class="fex-bar" data-bind-hidden="!dirty.value">
4
+ <span class="fex-chip">Unsaved changes</span>
5
+ <small class="fex-note">Ctrl+S saves. Leaving asks first and keeps a draft of this form.</small>
6
+ <span class="fex-spacer"></span>
7
+ <button type="button" class="btn btn-sm btn-ghost" data-on-click="discard">Discard</button>
8
+ <button type="button" id="save-form-btn" class="btn btn-sm btn-primary" data-on-click="save" data-bind-disabled="saving.value">
9
+ <span data-icon="save" data-icon-size="13"></span> Save</button>
11
10
  </div>
12
11
 
13
12
  <div class="tabs" id="editor-tabs">
@@ -24,20 +23,20 @@
24
23
  <div class="fb-fields-layout">
25
24
  <div class="card mb-4">
26
25
  <div class="card-header" style="display:flex;justify-content:space-between;align-items:center;">
27
- <h2>Fields</h2>
26
+ <h2>Fields <span data-help-title="Fields" data-help="Click a field to open it; drag its header to reorder. Right-click a field (or ⋮, or Shift+F10 on it) to edit it, open its logic and triggers, duplicate it, move it up or down, copy its name or delete it. A Page Break splits the form into wizard steps; a Spacer adds room between fields."></span></h2>
28
27
  <div id="add-element-dropdown" style="position:relative;">
29
28
  <button id="add-element-btn" class="btn btn-ghost btn-sm" type="button">
30
29
  <span data-icon="plus"></span> Add <span data-icon="chevron-down" data-icon-size="12"
31
30
  style="opacity:.6;margin-left:.1rem;display:inline-flex;align-items:center;vertical-align:middle;"></span>
32
31
  </button>
33
32
  <div id="add-element-menu"
34
- style="display:none;position:absolute;right:0;top:calc(100% + .2rem);background:var(--dm-card-bg,#1e1e2e);border:1px solid var(--dm-border,#333);border-radius:6px;min-width:140px;z-index:100;box-shadow:var(--dm-shadow-lg);overflow:hidden;">
33
+ style="display:none;position:absolute;right:0;top:calc(100% + .2rem);background:var(--dm-card-bg);border:1px solid var(--dm-border);border-radius:6px;min-width:140px;z-index:100;box-shadow:var(--dm-shadow-lg);overflow:hidden;">
35
34
  <button id="add-field-btn" type="button"
36
- style="display:block;width:100%;text-align:left;padding:.5rem .85rem;background:none;border:none;color:inherit;cursor:pointer;font-size:.875rem;border-bottom:1px solid var(--dm-border,#333);">
35
+ style="display:block;width:100%;text-align:left;padding:.5rem .85rem;background:none;border:none;color:inherit;cursor:pointer;font-size:.875rem;border-bottom:1px solid var(--dm-border);">
37
36
  Field
38
37
  </button>
39
38
  <button id="add-spacer-btn" type="button"
40
- style="display:block;width:100%;text-align:left;padding:.5rem .85rem;background:none;border:none;color:inherit;cursor:pointer;font-size:.875rem;border-bottom:1px solid var(--dm-border,#333);">
39
+ style="display:block;width:100%;text-align:left;padding:.5rem .85rem;background:none;border:none;color:inherit;cursor:pointer;font-size:.875rem;border-bottom:1px solid var(--dm-border);">
41
40
  Spacer
42
41
  </button>
43
42
  <button id="add-page-break-btn" type="button"
@@ -49,8 +48,8 @@
49
48
  </div>
50
49
  <div class="card-body" style="padding:0;">
51
50
  <div id="fields-list" style="padding:1rem;">
52
- <p class="text-muted" id="fields-empty-msg" style="text-align:center;padding:2rem 0;">No fields yet. Click
53
- "Add Field" to get started.</p>
51
+ <p class="text-muted" id="fields-empty-msg" style="text-align:center;padding:2rem 0;">No fields yet - use
52
+ Add to put the first one in.</p>
54
53
  </div>
55
54
  </div>
56
55
  </div>
@@ -58,9 +57,9 @@
58
57
  <aside class="fb-preview-pane">
59
58
  <div class="card mb-4" id="preview-card">
60
59
  <div class="card-header" style="display:flex;justify-content:space-between;align-items:center;">
61
- <h2><span data-icon="eye"></span> Preview</h2>
60
+ <h2><span data-icon="eye"></span> Preview <span data-help-title="Preview" data-help="The form as visitors will see it, updated as you edit - conditional logic and triggers included. Once the form is saved, a test submission here is stored like a real one."></span></h2>
62
61
  <span id="preview-test-badge"
63
- style="display:none;font-size:.75rem;padding:.2rem .6rem;border-radius:999px;background:color-mix(in srgb, var(--dm-primary) 15%, transparent);color:var(--dm-primary,#6366f1);">Test Mode - submissions are stored</span>
62
+ style="display:none;font-size:.75rem;padding:.2rem .6rem;border-radius:999px;background:color-mix(in srgb, var(--dm-primary) 15%, transparent);color:var(--dm-primary);">Test Mode - submissions are stored</span>
64
63
  </div>
65
64
  <div class="card-body">
66
65
  <div id="preview-container"></div>
@@ -83,7 +82,7 @@
83
82
  <input id="field-title" type="text" class="form-input" placeholder="My Form">
84
83
  </div>
85
84
  <div class="col-5">
86
- <label class="form-label">Slug</label>
85
+ <label class="form-label">Slug <span data-help="The form's id - how pages embed it ([form name=&quot;slug&quot;]) and where its submissions are filed. Made from the title for a new form; changing it later breaks existing embeds."></span></label>
87
86
  <input id="field-slug" type="text" class="form-input" placeholder="my-form">
88
87
  <p class="form-hint text-muted" style="margin-top:.3rem;font-size:.8rem;">Used in embed: <code>data-form="slug"</code>
89
88
  </p>
@@ -98,7 +97,7 @@
98
97
  </div>
99
98
  <div class="row mt-3">
100
99
  <div class="col-6">
101
- <label class="form-label">Project</label>
100
+ <label class="form-label">Project <span data-help="Files the form under a project: it shows in that project's lists, and users limited to other projects cannot see it."></span></label>
102
101
  <select id="field-project" class="form-input">
103
102
  <option value="">- none -</option>
104
103
  </select>
@@ -131,7 +130,7 @@
131
130
  placeholder="Thank you for your submission."></textarea>
132
131
  </div>
133
132
  <div class="mb-3">
134
- <label class="form-label">Success Redirect URL</label>
133
+ <label class="form-label">Success Redirect URL <span data-help="Where the visitor goes after a successful submission - a page on this site (/thank-you) or a full address. Leave empty to show the success message in place."></span></label>
135
134
  <input id="setting-success-redirect" type="text" class="form-input"
136
135
  placeholder="/thank-you">
137
136
  <p class="form-hint text-muted" style="margin-top:.3rem;font-size:.8rem;">
@@ -139,7 +138,7 @@
139
138
  </p>
140
139
  </div>
141
140
  <div class="mb-3">
142
- <label class="form-label">Layout</label>
141
+ <label class="form-label">Layout <span data-help="Grid lays fields out in columns (each field can span several); Stacked puts one field per row; Inline puts short fields side by side."></span></label>
143
142
  <select id="setting-layout" class="form-select">
144
143
  <option value="grid">Grid</option>
145
144
  <option value="stacked">Stacked</option>
@@ -181,7 +180,7 @@
181
180
  </label>
182
181
  </div>
183
182
  <div class="mb-3">
184
- <label class="form-label">Recipients</label>
183
+ <label class="form-label">Recipients <span data-help="Who is emailed each submission - one or more addresses, comma-separated. Sent through the site's SMTP settings (System › Site Settings › Email); without them nothing is delivered."></span></label>
185
184
  <input id="action-email-recipients" type="text" class="form-input" placeholder="admin@example.com">
186
185
  <p class="form-hint text-muted" style="margin-top:.3rem;font-size:.8rem;">Comma-separated. Uses global
187
186
  SMTP settings.</p>
@@ -203,7 +202,7 @@
203
202
  </label>
204
203
  </div>
205
204
  <div class="mb-3">
206
- <label class="form-label">URL</label>
205
+ <label class="form-label">URL <span data-help="Each submission is sent here as JSON. The address is not kept in the editor's draft, in case it carries a secret token."></span></label>
207
206
  <input id="action-webhook-url" type="url" class="form-input"
208
207
  placeholder="https://hooks.example.com/form">
209
208
  </div>
@@ -222,7 +221,7 @@
222
221
  <div class="card-header"><h2>CMS Action</h2></div>
223
222
  <div class="card-body">
224
223
  <div class="mb-3">
225
- <label class="form-label">Action on Submit</label>
224
+ <label class="form-label">Action on Submit <span data-help="A CMS Action (Data › Actions) run after the submission is stored - for example to create an entry elsewhere or move one on a step. It needs MongoDB; a failure never loses the submission."></span></label>
226
225
  <select id="action-cms-slug" class="form-select">
227
226
  <option value="">- None -</option>
228
227
  </select>
@@ -247,7 +246,7 @@
247
246
  that fill a hidden field.</p>
248
247
  </div>
249
248
  <div>
250
- <label class="form-label">Rate Limit (per minute)</label>
249
+ <label class="form-label">Rate Limit (per minute) <span data-help="How many submissions one visitor's address may make in a minute; more are refused. Keeps a form from being flooded."></span></label>
251
250
  <input id="setting-rate-limit" type="number" class="form-input" min="1" max="60" value="3">
252
251
  <p class="form-hint text-muted" style="margin-top:.3rem;font-size:.8rem;">Max submissions per IP per
253
252
  minute.</p>
@@ -258,3 +257,19 @@
258
257
 
259
258
  </div>
260
259
  </div>
260
+
261
+ <style>
262
+ .fex-bar { display: flex; align-items: center; gap: .6rem; flex-wrap: wrap; padding: .5rem .75rem; margin-bottom: .75rem;
263
+ border: 1px solid color-mix(in srgb, var(--dm-warning) 45%, var(--dm-border)); border-radius: var(--dm-radius-md);
264
+ background: color-mix(in srgb, var(--dm-warning) 10%, transparent); position: sticky; top: 0; z-index: 5; }
265
+ .fex-bar[hidden] { display: none !important; }
266
+ .fex-chip { font-size: var(--dm-text-xs); font-weight: 600; padding: .1rem .55rem; border-radius: 999px;
267
+ background: color-mix(in srgb, var(--dm-warning) 22%, transparent); color: var(--dm-warning); white-space: nowrap; }
268
+ .fex-note { color: var(--dm-text-muted); }
269
+ .fex-spacer { flex: 1; }
270
+ .fex-bar .btn { display: inline-flex; align-items: center; gap: .3rem; }
271
+ .fex-kebab { border: 0; background: none; cursor: pointer; font-size: var(--dm-text-lg); line-height: 1; color: var(--dm-text-muted);
272
+ border-radius: var(--dm-radius-sm); padding: 0 .35rem; flex-shrink: 0; }
273
+ .fex-kebab:hover { color: var(--dm-primary); }
274
+ #fields-list .fb-field-card:focus-visible { outline: 2px solid var(--dm-primary); outline-offset: 2px; }
275
+ </style>
@@ -1,30 +1,82 @@
1
- <div class="view-header">
2
- <h1><span data-icon="inbox"></span> <span id="submissions-title">Submissions</span></h1>
3
- <div style="display:flex;gap:.5rem;align-items:center;">
4
- <a href="#/forms" class="btn btn-ghost btn-sm">
5
- <span data-icon="arrow-left"></span> All Forms
6
- </a>
7
- <button id="export-btn" class="btn btn-ghost btn-sm">
8
- <span data-icon="download"></span> Export
9
- </button>
10
- <button id="clear-all-btn" class="btn btn-danger btn-sm">
11
- <span data-icon="trash"></span> Clear All
12
- </button>
13
- </div>
14
- </div>
1
+ <!-- Form submissions. Bound with M.applyBindings - see admin/views/form-submissions.js, which also inserts the
2
+ row template (the router renders a templateUrl through _.render, eating any {{…}}). No page heading:
3
+ the Tool banner ("Forms › <form> submissions") is the heading; All forms, Export and Clear all are banner
4
+ actions. -->
5
+ <div class="fsx">
6
+ <p class="fsx-note"><span data-bind-text="intro.value">What people sent through this form.</span>
7
+ <span data-help-title="Submissions" data-help="Every answer sent through this form, newest first, read from the collection the form saves to (its collection action, or one named after the form). Click one for everything that was sent; right-click it (or ⋮, or Shift+F10) to copy it, copy the sender's email, mark it as spam or delete it. Spam is kept out of the inbox but not deleted - you can put it back."></span></p>
15
8
 
16
- <div class="card mb-3">
17
- <div class="card-body" style="display:flex;gap:.75rem;align-items:center;flex-wrap:wrap;">
18
- <input id="sub-search" type="text" class="form-input" placeholder="Search submissions…" style="flex:1;min-width:200px;">
19
- <input id="sub-date-from" type="date" class="form-input" style="width:auto;">
20
- <span style="color:var(--dm-text-muted,#888);">to</span>
21
- <input id="sub-date-to" type="date" class="form-input" style="width:auto;">
22
- <span id="sub-count" class="text-muted" style="white-space:nowrap;font-size:.85rem;"></span>
9
+ <div class="fsx-card">
10
+ <div class="fsx-bar">
11
+ <div class="fsx-seg" role="group" aria-label="Show">
12
+ <button type="button" data-on-click="showInbox" data-bind-class="filter.value === 'inbox' && 'is-on'">Inbox <b data-bind-text="counts.value.inbox"></b></button>
13
+ <button type="button" data-on-click="showNew" data-bind-class="filter.value === 'new' && 'is-on'">New this week <b data-bind-text="counts.value.new"></b></button>
14
+ <button type="button" data-on-click="showSpam" data-bind-class="filter.value === 'spam' && 'is-on'">Spam <b data-bind-text="counts.value.spam"></b></button>
15
+ </div>
16
+ <input type="search" class="form-input fsx-search" placeholder="Search answers…" aria-label="Search answers" data-model="query.value">
17
+ <span class="fsx-dates">
18
+ <input type="date" class="form-input" aria-label="From" data-model="from.value">
19
+ <span class="fsx-muted">to</span>
20
+ <input type="date" class="form-input" aria-label="To" data-model="to.value">
21
+ <button type="button" class="btn btn-sm btn-ghost" data-on-click="clearDates" data-bind-hidden="!from.value && !to.value" title="Clear the dates">×</button>
22
+ </span>
23
+ <span data-help-title="Finding one" data-help="Search looks through every answer. The dates are the day it was sent, both ends included. New this week is the last 7 days - the same count as the badge on Forms in the sidebar."></span>
24
+ <span class="fsx-spacer"></span>
25
+ <small class="fsx-muted" data-bind-text="summary.value"></small>
26
+ </div>
27
+ <p class="fsx-note" data-bind-hidden="!loading.value">Loading…</p>
28
+ <p class="fsx-note is-bad" data-bind-hidden="!loadError.value" data-bind-text="loadError.value"></p>
29
+ <p class="fsx-note" data-bind-hidden="loading.value || loadError.value || rows.value.length" data-bind-text="emptyText.value"></p>
30
+ <div class="fsx-list" data-each="rows.value key=id"></div>
23
31
  </div>
24
32
  </div>
25
33
 
26
- <div class="card">
27
- <div class="card-body">
28
- <div id="submissions-table"></div>
29
- </div>
30
- </div>
34
+ <style>
35
+ .fsx { display: flex; flex-direction: column; gap: .75rem; }
36
+ .fsx [hidden], .fsx-detail [hidden] { display: none !important; }
37
+ .fsx-note { margin: 0; color: var(--dm-text-muted); font-size: var(--dm-text-sm); }
38
+ .fsx-note.is-bad { color: var(--dm-danger); }
39
+ .fsx-muted { color: var(--dm-text-muted); font-size: var(--dm-text-sm); }
40
+ .fsx-card { border: 1px solid var(--dm-border); border-radius: var(--dm-radius-md); padding: .75rem .9rem; display: flex; flex-direction: column; gap: .6rem; }
41
+ .fsx-bar { display: flex; flex-wrap: wrap; gap: .5rem; align-items: center; }
42
+ .fsx-spacer { flex: 1; }
43
+ .fsx-search { flex: 1; min-width: 10rem; max-width: 18rem; }
44
+ .fsx-dates { display: inline-flex; align-items: center; gap: .35rem; }
45
+ .fsx-dates .form-input { width: auto; }
46
+ .fsx-seg { display: inline-flex; border: 1px solid var(--dm-border); border-radius: var(--dm-radius-md); overflow: hidden; }
47
+ .fsx-seg button { border: 0; background: none; color: var(--dm-text); padding: .3rem .7rem; cursor: pointer; font: inherit; font-size: var(--dm-text-sm); }
48
+ .fsx-seg button + button { border-left: 1px solid var(--dm-border); }
49
+ .fsx-seg button b { font-weight: 500; opacity: .65; margin-left: .2rem; }
50
+ .fsx-seg button:hover { background: var(--dm-table-hover-bg, color-mix(in srgb, var(--dm-text) 6%, transparent)); }
51
+ .fsx-seg button.is-on { background: var(--dm-primary); color: var(--dm-primary-text); }
52
+ .fsx-list { display: flex; flex-direction: column; }
53
+ .fsx-row { display: flex; align-items: center; gap: .75rem; padding: .55rem .35rem; border-bottom: 1px solid var(--dm-border); cursor: pointer; }
54
+ .fsx-row:last-child { border-bottom: 0; }
55
+ .fsx-row:hover, .fsx-row:focus-visible { background: var(--dm-table-hover-bg, color-mix(in srgb, var(--dm-text) 6%, transparent)); outline: none; }
56
+ .fsx-dot { width: .55rem; height: .55rem; border-radius: 50%; flex-shrink: 0; background: transparent; }
57
+ .fsx-row.is-new .fsx-dot { background: var(--dm-success); }
58
+ .fsx-row.is-spam .fsx-main strong { color: var(--dm-text-muted); text-decoration: line-through; }
59
+ .fsx-main { flex: 1; min-width: 0; display: flex; flex-direction: column; }
60
+ .fsx-main strong, .fsx-main small { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
61
+ .fsx-main small { color: var(--dm-text-muted); font-size: var(--dm-text-xs); }
62
+ .fsx-chips { display: inline-flex; gap: .3rem; flex-shrink: 0; }
63
+ .fsx-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;
64
+ background: color-mix(in srgb, var(--dm-text) 10%, transparent); color: var(--dm-text-muted); }
65
+ .fsx-chip.is-ok { background: color-mix(in srgb, var(--dm-success) 16%, transparent); color: var(--dm-success); }
66
+ .fsx-chip.is-warn { background: color-mix(in srgb, var(--dm-warning) 18%, transparent); color: var(--dm-warning); }
67
+ .fsx-when { min-width: 7.5rem; text-align: right; color: var(--dm-text-muted); font-size: var(--dm-text-sm); font-variant-numeric: tabular-nums; white-space: nowrap; }
68
+ .fsx-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; }
69
+ .fsx-kebab:hover { color: var(--dm-primary); }
70
+ /* The details slideover */
71
+ .fsx-detail { display: flex; flex-direction: column; gap: .85rem; }
72
+ .fsx-answers { display: flex; flex-direction: column; margin: 0; }
73
+ .fsx-answer { padding: .55rem 0; border-bottom: 1px solid var(--dm-border); }
74
+ .fsx-answer dt { font-size: var(--dm-text-xs); font-weight: 600; color: var(--dm-text-muted); margin-bottom: .15rem; }
75
+ .fsx-answer dd { margin: 0; white-space: pre-wrap; word-break: break-word; }
76
+ .fsx-facts { display: flex; flex-wrap: wrap; gap: .35rem .9rem; color: var(--dm-text-muted); font-size: var(--dm-text-xs); }
77
+ .fsx-actions { display: flex; flex-wrap: wrap; align-items: center; gap: .4rem; padding-top: .6rem; border-top: 1px solid var(--dm-border); }
78
+ .fsx-actions .btn { display: inline-flex; align-items: center; gap: .3rem; }
79
+ .fsx-export { display: flex; flex-direction: column; gap: .6rem; }
80
+ .fsx-export .btn { justify-content: flex-start; display: inline-flex; align-items: center; gap: .4rem; }
81
+ @media (max-width: 720px) { .fsx-when { display: none; } .fsx-dates { flex-basis: 100%; } }
82
+ </style>