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,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>
@@ -1,49 +1,132 @@
1
- <div class="view-header">
2
- <h1><span data-icon="layout"></span> Layouts</h1>
3
- </div>
1
+ <!-- Layouts. Bound with M.applyBindings - see admin/views/layouts.js, which also inserts the row template
2
+ (the router renders a templateUrl through _.render, eating any {{…}}). No page heading: the Tool
3
+ banner above is the heading, and New layout is a banner action. -->
4
+ <div class="lyx">
5
+ <div class="tabs lyx-tabs">
6
+ <div class="tab-list">
7
+ <button type="button" class="tab-item active">Layouts <span class="lyx-count" data-bind-text="counts.value.all"></span></button>
8
+ <button type="button" class="tab-item">Options <span class="lyx-dot" data-bind-hidden="!optDirty.value" title="Unsaved changes">●</span></button>
9
+ </div>
4
10
 
5
- <div class="tabs" id="layouts-tabs">
6
- <div class="tab-list">
7
- <button class="tab-item active" data-tab="panel-presets">Layout Presets</button>
8
- <button class="tab-item" data-tab="panel-options">Layout Options</button>
9
- </div>
10
- <div class="tab-content">
11
+ <!-- Layouts -->
12
+ <div class="tab-panel active">
13
+ <p class="lyx-note">A layout sets a page's width, background, and whether it has the navbar, footer and sidebar.
14
+ Pages choose one in the page editor; a page that names none uses Default.
15
+ <span data-help-title="Layouts" data-help="The built-in layouts can be edited but not deleted. A layout you make gets a key from its name (Dark Landing becomes dark-landing) - that key is what a page's layout: line names, and it does not change if you rename the layout later. Deleting a layout that pages use leaves those pages on Default."></span></p>
11
16
 
12
- <div class="tab-panel active" id="panel-presets">
13
- <div class="view-actions mb-4"
14
- style="display:flex;align-items:center;justify-content:space-between;gap:1rem;flex-wrap:wrap;">
15
- <p class="text-muted" style="margin:0;">Layout presets define the structure, width, and background of pages.
16
- Assign a layout to each page via the page editor.</p>
17
- <button id="create-layout-btn" class="btn btn-primary"><span data-icon="plus"></span> New Layout</button>
18
- </div>
19
- <div id="presets-grid" class="presets-grid"></div>
20
- </div>
17
+ <div class="lyx-alert" data-bind-hidden="!missing.value.length">
18
+ <span data-icon="alert-triangle" data-icon-size="16"></span>
19
+ <span data-bind-text="missingText.value"></span>
20
+ <button type="button" class="btn btn-sm btn-ghost" data-on-click="showMissing">Show them</button>
21
+ </div>
21
22
 
22
- <div class="tab-panel" id="panel-options">
23
- <div class="card mb-4">
24
- <div class="card-header"><h3>Spacer Shortcode</h3></div>
25
- <div class="card-body">
26
- <p class="text-muted mb-3">
27
- The <code>[spacer /]</code> shortcode inserts a vertical gap. Set the default height here;
28
- individual spacers can override it with <code>size="N"</code>.
29
- </p>
30
- <div class="form-group">
31
- <label class="form-label" for="spacer-size-input">Default spacer height (px)</label>
32
- <input id="spacer-size-input" type="number" class="form-input" style="max-width:120px" min="0" max="500"
33
- step="1" value="8">
34
- </div>
35
- <div class="form-group mt-3">
36
- <label class="form-label" for="spacer-class-input">Default spacer CSS class</label>
37
- <input id="spacer-class-input" type="text" class="form-input" style="max-width:240px"
38
- placeholder="e.g. my-gap">
39
- <small class="text-muted">Applied to every <code>[spacer /]</code> unless overridden per-instance with
40
- <code>class="…"</code>.</small>
41
- </div>
42
- <button id="save-options-btn" class="btn btn-primary mt-3"><span data-icon="save"></span> Save Options
43
- </button>
23
+ <div class="lyx-card">
24
+ <div class="lyx-bar">
25
+ <div class="lyx-seg" role="group" aria-label="Show">
26
+ <button type="button" data-on-click="showAll" data-bind-class="filter.value === 'all' && 'is-on'">All <b data-bind-text="counts.value.all"></b></button>
27
+ <button type="button" data-on-click="showBuiltin" data-bind-class="filter.value === 'builtin' && 'is-on'">Built in <b data-bind-text="counts.value.builtin"></b></button>
28
+ <button type="button" data-on-click="showCustom" data-bind-class="filter.value === 'custom' && 'is-on'">Yours <b data-bind-text="counts.value.custom"></b></button>
29
+ </div>
30
+ <input type="search" class="form-input lyx-search" placeholder="Search layouts…" aria-label="Search layouts" data-model="query.value">
31
+ <span data-help-title="The list" data-help="Click a layout to edit it. Right-click it (or ⋮, or Shift+F10) to see the pages that use it, duplicate it, copy the line a page's frontmatter needs, or delete it. The little drawing shows its shape: the navbar and footer bars, how wide the content is, and its background colour."></span>
32
+ </div>
33
+ <p class="lyx-note" data-bind-hidden="!loading.value">Loading…</p>
34
+ <p class="lyx-note is-bad" data-bind-hidden="!loadError.value" data-bind-text="loadError.value"></p>
35
+ <p class="lyx-note" data-bind-hidden="loading.value || loadError.value || rows.value.length">No layouts match.</p>
36
+ <div class="lyx-list" data-each="rows.value key=key"></div>
37
+ </div>
44
38
  </div>
45
- </div>
46
- </div>
47
39
 
48
- </div>
40
+ <!-- Options -->
41
+ <div class="tab-panel">
42
+ <div class="lyx-card">
43
+ <div class="lyx-card-title">Spacer shortcode
44
+ <span data-help-title="[spacer /]" data-help="The [spacer /] shortcode puts a vertical gap in a page. These are its defaults; a single spacer can still say size=&quot;40&quot; or class=&quot;…&quot; for itself."></span></div>
45
+ <div class="lyx-grid">
46
+ <div>
47
+ <label class="form-label" for="lyx-o-size">Default height (px)</label>
48
+ <input id="lyx-o-size" type="number" min="0" max="500" step="1" class="form-input" data-model="spacerSize.value">
49
+ <small class="lyx-err" data-bind-hidden="!optErrSize.value" data-bind-text="optErrSize.value"></small>
50
+ </div>
51
+ <div>
52
+ <label class="form-label" for="lyx-o-class">Default CSS class</label>
53
+ <input id="lyx-o-class" type="text" class="form-input" placeholder="e.g. my-gap" data-model="spacerClass.value">
54
+ <small class="lyx-err" data-bind-hidden="!optErrClass.value" data-bind-text="optErrClass.value"></small>
55
+ </div>
56
+ </div>
57
+ <div class="lyx-actions">
58
+ <small class="lyx-hint" data-bind-text="optStatus.value"></small>
59
+ <span class="lyx-spacer"></span>
60
+ <button type="button" class="btn btn-sm btn-ghost" data-on-click="resetOptions" data-bind-hidden="!optDirty.value">Discard</button>
61
+ <button type="button" class="btn btn-sm btn-primary" data-on-click="saveOptions" data-bind-disabled="optCannot.value">
62
+ <span data-icon="save" data-icon-size="13"></span> Save options</button>
63
+ </div>
64
+ </div>
65
+ </div>
66
+ </div>
49
67
  </div>
68
+
69
+ <style>
70
+ .lyx { display: flex; flex-direction: column; gap: .75rem; }
71
+ .lyx [hidden], .lyx-form [hidden] { display: none !important; }
72
+ .lyx-note { margin: 0 0 .6rem; color: var(--dm-text-muted); font-size: var(--dm-text-sm); }
73
+ .lyx-note.is-bad { color: var(--dm-danger); }
74
+ .lyx-count { opacity: .65; font-size: var(--dm-text-xs); margin-left: .2rem; }
75
+ .lyx-dot { color: var(--dm-warning); font-size: var(--dm-text-xs); margin-left: .25rem; }
76
+ .lyx-alert { display: flex; gap: .5rem; align-items: center; margin: 0 0 .6rem; padding: .5rem .75rem; border-radius: var(--dm-radius-md);
77
+ background: color-mix(in srgb, var(--dm-warning) 16%, transparent); color: var(--dm-text); font-size: var(--dm-text-sm); }
78
+ .lyx-card { border: 1px solid var(--dm-border); border-radius: var(--dm-radius-md); padding: .75rem .9rem; display: flex; flex-direction: column; gap: .6rem; }
79
+ .lyx-card-title { display: flex; align-items: center; gap: .3rem; font-weight: 600; }
80
+ .lyx-bar { display: flex; flex-wrap: wrap; gap: .5rem; align-items: center; }
81
+ .lyx-search { flex: 1; min-width: 10rem; max-width: 22rem; }
82
+ .lyx-seg { display: inline-flex; border: 1px solid var(--dm-border); border-radius: var(--dm-radius-md); overflow: hidden; }
83
+ .lyx-seg button { border: 0; background: none; color: var(--dm-text); padding: .3rem .7rem; cursor: pointer; font: inherit; font-size: var(--dm-text-sm); }
84
+ .lyx-seg button + button { border-left: 1px solid var(--dm-border); }
85
+ .lyx-seg button b { font-weight: 500; opacity: .65; margin-left: .2rem; }
86
+ .lyx-seg button:hover { background: var(--dm-table-hover-bg, color-mix(in srgb, var(--dm-text) 6%, transparent)); }
87
+ .lyx-seg button.is-on { background: var(--dm-primary); color: var(--dm-primary-text); }
88
+ .lyx-list { display: flex; flex-direction: column; }
89
+ .lyx-row { display: flex; align-items: center; gap: .75rem; padding: .55rem .35rem; border-bottom: 1px solid var(--dm-border); cursor: pointer; }
90
+ .lyx-row:last-child { border-bottom: 0; }
91
+ .lyx-row:hover, .lyx-row:focus-visible { background: var(--dm-table-hover-bg, color-mix(in srgb, var(--dm-text) 6%, transparent)); outline: none; }
92
+ /* The little drawing of a layout: navbar, content column, footer, background. */
93
+ .lyx-shape { flex-shrink: 0; width: 3rem; height: 2.2rem; border: 1px solid var(--dm-border); border-radius: var(--dm-radius-sm);
94
+ background: var(--dm-surface); display: flex; flex-direction: column; overflow: hidden; }
95
+ .lyx-shape-nav, .lyx-shape-foot { height: 5px; background: color-mix(in srgb, var(--dm-text-muted) 55%, transparent); }
96
+ .lyx-shape-body { flex: 1; display: flex; justify-content: center; padding: 3px 0; gap: 2px; }
97
+ .lyx-shape-col { background: color-mix(in srgb, var(--dm-primary) 45%, transparent); border-radius: 1px; }
98
+ .lyx-shape-side { width: 18%; background: color-mix(in srgb, var(--dm-text-muted) 35%, transparent); border-radius: 1px; }
99
+ .lyx-main { flex: 1; min-width: 0; display: flex; flex-direction: column; }
100
+ .lyx-main strong, .lyx-main small { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
101
+ .lyx-main small { color: var(--dm-text-muted); font-size: var(--dm-text-xs); }
102
+ .lyx-chips { display: inline-flex; gap: .3rem; flex-shrink: 0; }
103
+ .lyx-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;
104
+ background: color-mix(in srgb, var(--dm-text) 10%, transparent); color: var(--dm-text-muted); }
105
+ .lyx-chip.is-info { background: color-mix(in srgb, var(--dm-info, var(--dm-primary)) 14%, transparent); color: var(--dm-info, var(--dm-primary)); }
106
+ .lyx-chip.is-ok { background: color-mix(in srgb, var(--dm-success) 16%, transparent); color: var(--dm-success); }
107
+ .lyx-chip.is-muted { opacity: .7; }
108
+ .lyx-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; }
109
+ .lyx-kebab:hover { color: var(--dm-primary); }
110
+ .lyx-grid { display: grid; grid-template-columns: 1fr 1fr; gap: .85rem; }
111
+ .lyx-wide { grid-column: 1 / -1; }
112
+ .lyx-err { display: block; color: var(--dm-danger); font-size: var(--dm-text-xs); margin-top: .25rem; }
113
+ .lyx-hint { display: block; color: var(--dm-text-muted); font-size: var(--dm-text-xs); margin-top: .25rem; }
114
+ .lyx-actions { display: flex; align-items: center; gap: .4rem; padding-top: .6rem; border-top: 1px solid var(--dm-border); }
115
+ .lyx-actions .btn { display: inline-flex; align-items: center; gap: .3rem; }
116
+ .lyx-spacer { flex: 1; }
117
+ /* The editor slideover */
118
+ .lyx-form { display: flex; flex-direction: column; gap: .85rem; }
119
+ .lyx-form .form-label { display: flex; align-items: center; gap: .3rem; }
120
+ .lyx-switches { display: flex; flex-wrap: wrap; gap: 1.25rem; }
121
+ .lyx-switches label { display: inline-flex; align-items: center; gap: .4rem; cursor: pointer; }
122
+ .lyx-colour { display: flex; align-items: center; gap: .5rem; }
123
+ .lyx-colour input[type="color"] { width: 2.4rem; height: 2rem; padding: 2px; border: 1px solid var(--dm-border); border-radius: var(--dm-radius-sm); background: transparent; cursor: pointer; }
124
+ .lyx-preview { display: flex; align-items: center; gap: .75rem; padding: .6rem; border: 1px dashed var(--dm-border); border-radius: var(--dm-radius-md); }
125
+ .lyx-preview .lyx-shape { width: 5rem; height: 3.6rem; }
126
+ .lyx-pages { display: flex; flex-direction: column; }
127
+ .lyx-pages a { display: flex; justify-content: space-between; gap: .75rem; padding: .45rem .25rem; border-bottom: 1px solid var(--dm-border); color: var(--dm-text); text-decoration: none; }
128
+ .lyx-pages a:last-child { border-bottom: 0; }
129
+ .lyx-pages a:hover { background: var(--dm-table-hover-bg, color-mix(in srgb, var(--dm-text) 6%, transparent)); }
130
+ .lyx-pages small { color: var(--dm-text-muted); }
131
+ @media (max-width: 640px) { .lyx-grid { grid-template-columns: 1fr; } .lyx-chips .lyx-chip:not(.is-info) { display: none; } }
132
+ </style>
@@ -1,10 +1,15 @@
1
- <div class="page-header">
2
- <h2><span data-icon="menu"></span> <span id="me-title">Edit menu</span></h2>
3
- <div class="header-actions">
4
- <a class="btn btn-secondary" href="#/menus"><span data-icon="arrow-left"></span> Back</a>
5
- <button class="btn btn-primary" id="me-save"><span data-icon="check"></span> Save</button>
6
- </div>
7
- </div>
1
+ <!-- Menus › Edit. See admin/js/views/menu-editor.js. No page heading: the Tool banner above is the
2
+ heading ("Menus › <name>"), with All menus, Menu locations and Save as its actions. -->
3
+ <div class="mex">
4
+ <p class="mex-alert is-dirty" data-bind-hidden="!dirty.value">
5
+ <span data-icon="edit-3" data-icon-size="16"></span>
6
+ <span>Unsaved changes - Save (Ctrl+S) to keep them. Leave and they are kept as a draft for next time.</span>
7
+ <span class="mex-spacer"></span>
8
+ <button type="button" class="btn btn-sm btn-ghost" data-on-click="discard">Discard</button>
9
+ <button type="button" class="btn btn-sm btn-primary" data-on-click="save" data-bind-disabled="saving.value">Save</button></p>
10
+ <p class="mex-alert is-bad" data-bind-hidden="!problems.value">
11
+ <span data-icon="alert-triangle" data-icon-size="16"></span>
12
+ <span data-bind-text="problems.value"></span></p>
8
13
 
9
14
  <div class="card">
10
15
  <div class="card-body" style="padding-top:0;">
@@ -17,10 +22,13 @@
17
22
  </div>
18
23
  <div class="tab-content">
19
24
  <div class="tab-panel active" id="me-items" data-panel="me-items">
20
- <div class="toolbar">
21
- <button class="btn btn-sm btn-secondary" id="me-add-item"><span data-icon="plus"></span> Add item</button>
22
- <button class="btn btn-sm btn-ghost" id="me-add-separator"><span data-icon="minus"></span> Add separator</button>
23
- <button class="btn btn-sm btn-ghost" id="me-add-spacer"><span data-icon="move"></span> Add spacer</button>
25
+ <div class="toolbar mex-toolbar">
26
+ <button type="button" class="btn btn-sm btn-secondary" id="me-add-item" data-on-click="addItem"><span data-icon="plus"></span> Add item</button>
27
+ <button type="button" class="btn btn-sm btn-ghost" id="me-add-separator" data-on-click="addSeparator"><span data-icon="minus"></span> Add separator</button>
28
+ <button type="button" class="btn btn-sm btn-ghost" id="me-add-spacer" data-on-click="addSpacer"><span data-icon="move"></span> Add spacer</button>
29
+ <span class="mex-spacer"></span>
30
+ <small class="mex-note"><span data-bind-text="itemCount.value"></span> items</small>
31
+ <span data-help-title="Items" data-help="Click an item to edit its label, link, icon, who sees it, badge and look. Right-click it (or ⋮, or Shift+F10) to add an item or a sub-item below it, move, indent, duplicate, hide or remove it. Drag the grip to move it, or drop it onto another item to nest it. With an item focused: Enter edits, Alt+Up / Down moves, Alt+Right / Left indents, Delete removes. Changes are kept only when you Save."></span>
24
32
  </div>
25
33
  <div id="me-items-list" class="menu-tree"></div>
26
34
  </div>
@@ -31,7 +39,8 @@
31
39
  </p>
32
40
  <div class="grid grid-cols-2 dm-pe-grid">
33
41
  <div class="form-group">
34
- <label for="me-variant">Variant</label>
42
+ <label for="me-variant">Variant
43
+ <span data-help="The colour scheme of the bar - transparent over the page, dark, light, or the theme's own default."></span></label>
35
44
  <select id="me-variant" name="variant" class="form-input">
36
45
  <option value="">- default -</option>
37
46
  <option value="transparent">Transparent</option>
@@ -41,7 +50,8 @@
41
50
  </select>
42
51
  </div>
43
52
  <div class="form-group">
44
- <label for="me-position">Position</label>
53
+ <label for="me-position">Position
54
+ <span data-help-title="Position" data-help="Sticky stays at the top as the page scrolls; fixed never moves; static scrolls away with the page. Floating lifts the menu off the page into a panel pinned to a corner or edge - its own options appear below."></span></label>
45
55
  <select id="me-position" name="position" class="form-input">
46
56
  <option value="">- default -</option>
47
57
  <option value="sticky">Sticky</option>
@@ -51,7 +61,8 @@
51
61
  </select>
52
62
  </div>
53
63
  <div class="form-group">
54
- <label for="me-orientation">Orientation</label>
64
+ <label for="me-orientation">Orientation
65
+ <span data-help="Horizontal lays the items in a row; vertical stacks them in a column - a side rail when this menu is the navbar."></span></label>
55
66
  <select id="me-orientation" name="orientation" class="form-input">
56
67
  <option value="">- default (horizontal) -</option>
57
68
  <option value="horizontal">Horizontal</option>
@@ -171,7 +182,8 @@
171
182
  </label>
172
183
  </div>
173
184
  <div class="form-group">
174
- <label for="me-highlight-hover">Hover effect</label>
185
+ <label for="me-highlight-hover">Hover effect
186
+ <span data-help-title="Highlighting" data-help="How items react under the pointer, and how the item for the page being viewed is marked. Set here for the whole menu; any single item can override both in its own editor."></span></label>
175
187
  <!-- Options are filled from HIGHLIGHT_HOVER in menu-decor.mjs on mount,
176
188
  so this list and the per-item editor's always match. -->
177
189
  <select id="me-highlight-hover" name="highlightHover" class="form-input"></select>
@@ -234,7 +246,8 @@
234
246
  </p>
235
247
  <div class="grid grid-cols-2 dm-pe-grid">
236
248
  <div class="form-group">
237
- <label for="me-bind-slot">Slot</label>
249
+ <label for="me-bind-slot">Slot
250
+ <span data-help-title="Binding" data-help="Makes this menu take over a slot (the navbar, a footer, an overlay…) on some pages only - a project's pages, or pages matching a pattern - while the menu mapped under Menu locations stays everywhere else. The most specific match wins."></span></label>
238
251
  <select id="me-bind-slot" name="bindSlot" class="form-input"></select>
239
252
  <small class="form-hint">Leave unset for no binding - this menu then only renders where it is mapped or where a <code>[menu]</code> shortcode places it.</small>
240
253
  </div>
@@ -261,7 +274,8 @@
261
274
  <div class="tab-panel" id="me-meta" data-panel="me-meta">
262
275
  <div class="grid grid-cols-2 dm-pe-grid">
263
276
  <div class="form-group">
264
- <label for="me-slug">Slug <span class="required">*</span></label>
277
+ <label for="me-slug">Slug <span class="required">*</span>
278
+ <span data-help="The menu's id - its file name and what [menu slug=&quot;…&quot;] names. Renaming it moves the menu and any location mapped to it."></span></label>
265
279
  <input type="text" id="me-slug" name="slug" class="form-input" required>
266
280
  <small class="form-hint">Lowercase alphanumeric + hyphen - affects file name</small>
267
281
  </div>
@@ -284,3 +298,29 @@
284
298
  </div>
285
299
  </div>
286
300
  </div>
301
+ </div>
302
+
303
+ <style>
304
+ /* Menus › Edit - see admin/js/views/menu-editor.js. Only --dm-* tokens. */
305
+ .mex { display: flex; flex-direction: column; gap: .75rem; }
306
+ .mex [hidden] { display: none !important; }
307
+ .mex-alert { margin: 0; display: flex; flex-wrap: wrap; gap: .5rem; align-items: center; padding: .6rem .8rem; border-radius: var(--dm-radius-md);
308
+ background: color-mix(in srgb, var(--dm-warning) 16%, transparent); color: var(--dm-text); }
309
+ .mex-alert.is-dirty { background: color-mix(in srgb, var(--dm-primary) 12%, transparent); }
310
+ .mex-alert.is-bad { background: color-mix(in srgb, var(--dm-danger) 14%, transparent); }
311
+ .mex-spacer { flex: 1; }
312
+ .mex-note { color: var(--dm-text-muted); font-size: var(--dm-text-sm); margin: 0; }
313
+ .mex-toolbar { display: flex; flex-wrap: wrap; align-items: center; gap: .4rem; }
314
+ .mex-toolbar .btn { display: inline-flex; align-items: center; gap: .3rem; }
315
+ /* Rows: grip, label, url, hide, edit, ⋮ - everything else is in the row's context menu. */
316
+ .mex .menu-tree-row--slim { grid-template-columns: 16px minmax(0, 1fr) auto repeat(3, auto); padding: 3px 4px; border-radius: var(--dm-radius-sm); cursor: pointer; }
317
+ .mex .menu-tree-row--separator, .mex .menu-tree-row--spacer { grid-template-columns: 16px minmax(80px, 1fr) auto; padding: 2px 4px; }
318
+ .mex .menu-tree-row:hover { background: var(--dm-table-hover-bg, color-mix(in srgb, var(--dm-text) 5%, transparent)); }
319
+ .mex .menu-tree-row:focus-visible { outline: 2px solid var(--dm-primary); outline-offset: 1px; }
320
+ .mex .menu-tree-row .me-row-label, .mex .menu-tree-row .me-row-url { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
321
+ .mex .me-hidden.active { color: var(--dm-warning); }
322
+ .mex .mex-untitled { color: var(--dm-danger); font-style: italic; font-weight: 500; }
323
+ .mex-kebab { font-size: var(--dm-text-lg); line-height: 1; color: var(--dm-text-muted); padding: 0 .4rem; }
324
+ .mex-kebab:hover { color: var(--dm-primary); }
325
+ @media (max-width: 640px) { .mex .menu-tree-row--slim .me-row-url { display: none; } .mex .menu-tree-row--slim { grid-template-columns: 16px minmax(0, 1fr) repeat(3, auto); } }
326
+ </style>