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,14 +1,57 @@
1
- <div class="page-header">
2
- <h2><span data-icon="map"></span> Menu locations</h2>
3
- <div class="header-actions">
4
- <a class="btn btn-secondary" href="#/menus"><span data-icon="arrow-left"></span> Back to menus</a>
5
- <button class="btn btn-primary" id="ml-save"><span data-icon="check"></span> Save</button>
6
- </div>
7
- </div>
8
- <p class="text-muted mb-4">Map each registered slot to a menu. Plugins can register additional slots via <code>hooks.registerMenuLocation()</code>.</p>
1
+ <!-- Menus › Locations. Bound with M.applyBindings - see admin/js/views/menu-locations.js, which also inserts
2
+ the row template (the router renders a templateUrl through _.render, eating any {{…}}). No page heading:
3
+ the Tool banner above is the heading; All menus and Save are its actions. -->
4
+ <div class="mlx">
5
+ <p class="mlx-alert is-dirty" data-bind-hidden="!dirty.value">
6
+ <span data-icon="edit-3" data-icon-size="16"></span>
7
+ <span>Unsaved changes - Save (Ctrl+S) to apply them. Leave and they are kept as a draft for next time.</span>
8
+ <span class="mlx-spacer"></span>
9
+ <button type="button" class="btn btn-sm btn-ghost" data-on-click="discard">Discard</button>
10
+ <button type="button" class="btn btn-sm btn-primary" data-on-click="save" data-bind-disabled="saving.value">Save</button></p>
9
11
 
10
- <div class="card">
11
- <div class="card-body">
12
- <div id="ml-table"></div>
12
+ <div class="mlx-card">
13
+ <div class="mlx-card-title">Where each menu shows
14
+ <span data-help-title="Menu locations" data-help="A slot is a place on the site a menu can fill - the navbar, the footers, the admin sidebar, the overlay (for floating panels), and any a plugin adds. Pick a menu for each. A menu's own Binding tab can take a slot over on some pages only; this is what shows everywhere else. The [menu location] shortcode draws whatever fills a slot."></span>
15
+ <span class="mlx-spacer"></span>
16
+ <small class="mlx-note" data-bind-text="summary.value"></small>
17
+ </div>
18
+ <p class="mlx-note" data-bind-hidden="!loading.value">Loading…</p>
19
+ <p class="mlx-note is-bad" data-bind-hidden="!loadError.value" data-bind-text="loadError.value"></p>
20
+ <p class="mlx-note" data-bind-hidden="loading.value || loadError.value || rows.value.length">No menu slots registered.</p>
21
+ <div class="mlx-list" data-each="rows.value key=slot"></div>
22
+ <p class="mlx-note">Click a slot to edit the menu in it; right-click (or ⋮, or Shift+F10) to clear it or copy its shortcode.
23
+ <span data-help="Nested deeper than the slot draws: that slot only shows so many levels, and the deeper items are simply left out there. Menu not found: the mapped menu was deleted or renamed outside the editor - pick another."></span></p>
13
24
  </div>
14
25
  </div>
26
+
27
+ <style>
28
+ /* Menus › Locations - see admin/js/views/menu-locations.js. Only --dm-* tokens. */
29
+ .mlx { display: flex; flex-direction: column; gap: .75rem; }
30
+ .mlx [hidden] { display: none !important; }
31
+ .mlx-alert { margin: 0; display: flex; flex-wrap: wrap; gap: .5rem; align-items: center; padding: .6rem .8rem; border-radius: var(--dm-radius-md);
32
+ background: color-mix(in srgb, var(--dm-primary) 12%, transparent); color: var(--dm-text); }
33
+ .mlx-spacer { flex: 1; }
34
+ .mlx-note { margin: 0; color: var(--dm-text-muted); font-size: var(--dm-text-sm); }
35
+ .mlx-note.is-bad { color: var(--dm-danger); }
36
+ .mlx-card { border: 1px solid var(--dm-border); border-radius: var(--dm-radius-md); padding: .75rem .9rem; display: flex; flex-direction: column; gap: .6rem; }
37
+ .mlx-card-title { display: flex; align-items: center; gap: .4rem; font-weight: 600; }
38
+ .mlx-list { display: flex; flex-direction: column; }
39
+ .mlx-row { display: grid; grid-template-columns: minmax(0, 1fr) auto minmax(10rem, 16rem) auto; gap: .75rem; align-items: center;
40
+ padding: .55rem .35rem; border-bottom: 1px solid var(--dm-border); cursor: pointer; }
41
+ .mlx-row:last-child { border-bottom: 0; }
42
+ .mlx-row:hover, .mlx-row:focus-visible { background: var(--dm-table-hover-bg, color-mix(in srgb, var(--dm-text) 6%, transparent)); outline: none; }
43
+ .mlx-row.is-changed { box-shadow: inset 3px 0 0 var(--dm-primary); }
44
+ .mlx-row.is-bad { box-shadow: inset 3px 0 0 var(--dm-warning); }
45
+ .mlx-main { min-width: 0; display: flex; flex-direction: column; }
46
+ .mlx-main small { color: var(--dm-text-muted); font-size: var(--dm-text-xs); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
47
+ .mlx-chips { display: inline-flex; flex-wrap: wrap; gap: .3rem; justify-content: flex-end; }
48
+ .mlx-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;
49
+ background: color-mix(in srgb, var(--dm-text) 10%, transparent); color: var(--dm-text-muted); }
50
+ .mlx-chip.is-warn { background: color-mix(in srgb, var(--dm-warning) 18%, transparent); color: var(--dm-warning); }
51
+ .mlx-chip.is-danger { background: color-mix(in srgb, var(--dm-danger) 16%, transparent); color: var(--dm-danger); }
52
+ .mlx-chip.is-changed { background: color-mix(in srgb, var(--dm-primary) 16%, transparent); color: var(--dm-primary); }
53
+ .mlx-select { margin: 0; }
54
+ .mlx-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; }
55
+ .mlx-kebab:hover { color: var(--dm-primary); }
56
+ @media (max-width: 720px) { .mlx-row { grid-template-columns: minmax(0, 1fr) auto; } .mlx-chips { grid-column: 1 / -1; justify-content: flex-start; } }
57
+ </style>
@@ -1,17 +1,105 @@
1
- <div class="view-header">
2
- <h1><span data-icon="user"></span> My Profile</h1>
3
- </div>
1
+ <!-- My Profile. Bound with M.applyBindings - see admin/views/my-profile.js, which also draws the profile
2
+ fields from the user-profiles schema. No page heading: the Tool banner above is the heading, and Save is
3
+ a banner action (and Ctrl+S). -->
4
+ <div class="mpx">
5
+ <p class="mpx-note is-bad" data-bind-hidden="!loadError.value" data-bind-text="loadError.value"></p>
6
+ <p class="mpx-note" data-bind-hidden="!loading.value">Loading…</p>
4
7
 
5
- <div class="card">
6
- <div class="card-header"><h2>Account</h2></div>
7
- <div class="card-body">
8
- <div id="account-form-container"></div>
8
+ <div class="mpx-dirty" data-bind-hidden="!dirty.value">
9
+ <span data-icon="edit" data-icon-size="14"></span>
10
+ <span>You have unsaved changes.</span>
11
+ <span class="mpx-spacer"></span>
12
+ <button type="button" class="btn btn-sm btn-ghost" data-on-click="discard">Discard</button>
13
+ <button type="button" class="btn btn-sm btn-primary" data-on-click="save" data-bind-disabled="cannotSave.value">
14
+ <span data-icon="check" data-icon-size="13"></span> Save</button>
9
15
  </div>
10
- </div>
11
16
 
12
- <div class="card mt-4" id="profile-card" style="display:none;">
13
- <div class="card-header"><h2>Profile</h2></div>
14
- <div class="card-body">
15
- <div id="profile-form-container"></div>
17
+ <div class="mpx-card mpx-you" data-bind-hidden="loading.value || loadError.value">
18
+ <span class="mpx-avatar" aria-hidden="true" data-bind-text="avatar.value"></span>
19
+ <span class="mpx-you-main">
20
+ <strong data-bind-text="shownName.value"></strong>
21
+ <small data-bind-text="shownEmail.value"></small>
22
+ <small class="mpx-facts" data-bind-text="factsText.value"></small>
23
+ </span>
24
+ <span class="mpx-chips">
25
+ <span class="mpx-chip is-ok" data-bind-text="rolesText.value" title="Your role - an administrator sets it"></span>
26
+ <span class="mpx-chip" data-bind-text="accessText.value" title="What you can see in the admin"></span>
27
+ </span>
28
+ <span data-help-title="Your account" data-help="Your role and which projects you can see are set by an administrator, under System › Users - they cannot be changed here. Everything below is yours to change."></span>
29
+ </div>
30
+
31
+ <div class="mpx-card" data-bind-hidden="loading.value || loadError.value">
32
+ <div class="mpx-card-title">Account
33
+ <span data-help="Your name is how you appear in the admin and on anything you author. Your email is what you sign in with, and where password resets are sent."></span></div>
34
+ <div class="mpx-grid">
35
+ <div>
36
+ <label class="form-label" for="mpx-name">Full name <span class="text-danger">*</span></label>
37
+ <input id="mpx-name" type="text" class="form-input" maxlength="100" autocomplete="name" placeholder="Jane Smith" data-model="name.value">
38
+ <small class="mpx-err" data-bind-hidden="!err.value.name" data-bind-text="err.value.name"></small>
39
+ </div>
40
+ <div>
41
+ <label class="form-label" for="mpx-email">Email <span class="text-danger">*</span></label>
42
+ <input id="mpx-email" type="email" class="form-input" maxlength="200" autocomplete="email" placeholder="jane@example.com" data-model="email.value">
43
+ <small class="mpx-err" data-bind-hidden="!err.value.email" data-bind-text="err.value.email"></small>
44
+ <small class="mpx-hint" data-bind-hidden="!emailChanged.value">You will sign in with the new address from now on.</small>
45
+ </div>
46
+ </div>
47
+ </div>
48
+
49
+ <div class="mpx-card" data-bind-hidden="loading.value || loadError.value">
50
+ <div class="mpx-card-title">Password
51
+ <span data-help-title="Changing your password" data-help="Leave both boxes empty to keep your current password. A new one needs at least 8 characters, typed twice. It is never kept as a draft - if you leave the screen, you type it again."></span></div>
52
+ <div class="mpx-grid">
53
+ <div>
54
+ <label class="form-label" for="mpx-pw">New password</label>
55
+ <input id="mpx-pw" type="password" data-bind-type="showPw.value ? 'text' : 'password'" class="form-input" autocomplete="new-password" placeholder="Leave empty to keep it" data-model="password.value">
56
+ <small class="mpx-err" data-bind-hidden="!err.value.password" data-bind-text="err.value.password"></small>
57
+ </div>
58
+ <div>
59
+ <label class="form-label" for="mpx-pw2">Type it again</label>
60
+ <input id="mpx-pw2" type="password" data-bind-type="showPw.value ? 'text' : 'password'" class="form-input" autocomplete="new-password" data-model="confirm.value">
61
+ <small class="mpx-err" data-bind-hidden="!err.value.confirm" data-bind-text="err.value.confirm"></small>
62
+ <small class="mpx-hint is-ok" data-bind-hidden="!pwMatch.value">✓ They match.</small>
63
+ </div>
64
+ </div>
65
+ <label class="mpx-show"><input type="checkbox" data-model="showPw.value"> Show what I type</label>
66
+ </div>
67
+
68
+ <div class="mpx-card" data-bind-hidden="loading.value || loadError.value || !hasProfile.value">
69
+ <div class="mpx-card-title">Profile
70
+ <span data-help="Extra details about you. Which ones are asked for is set by an administrator (the User Profiles collection)."></span></div>
71
+ <div class="mpx-grid mpx-profile"></div>
16
72
  </div>
17
73
  </div>
74
+
75
+ <style>
76
+ .mpx { display: flex; flex-direction: column; gap: .75rem; max-width: 60rem; }
77
+ .mpx [hidden] { display: none !important; }
78
+ .mpx-note { margin: 0; color: var(--dm-text-muted); font-size: var(--dm-text-sm); }
79
+ .mpx-note.is-bad { color: var(--dm-danger); }
80
+ .mpx-dirty { display: flex; align-items: center; gap: .5rem; padding: .5rem .8rem; border-radius: var(--dm-radius-md);
81
+ background: color-mix(in srgb, var(--dm-warning) 14%, transparent); color: var(--dm-text); position: sticky; top: 0; z-index: 2; }
82
+ .mpx-dirty .btn { display: inline-flex; align-items: center; gap: .3rem; }
83
+ .mpx-spacer { flex: 1; }
84
+ .mpx-card { border: 1px solid var(--dm-border); border-radius: var(--dm-radius-md); padding: .8rem .9rem; display: flex; flex-direction: column; gap: .7rem; }
85
+ .mpx-card-title { display: flex; align-items: center; gap: .35rem; font-weight: 600; }
86
+ .mpx-you { flex-direction: row; align-items: center; gap: .9rem; flex-wrap: wrap; }
87
+ .mpx-avatar { display: inline-flex; align-items: center; justify-content: center; width: 3rem; height: 3rem; border-radius: 50%; flex-shrink: 0;
88
+ font-weight: 700; color: var(--dm-primary); background: color-mix(in srgb, var(--dm-primary) 14%, transparent); }
89
+ .mpx-you-main { flex: 1; min-width: 12rem; display: flex; flex-direction: column; }
90
+ .mpx-you-main small { color: var(--dm-text-muted); font-size: var(--dm-text-xs); }
91
+ .mpx-facts { margin-top: .15rem; }
92
+ .mpx-chips { display: inline-flex; gap: .3rem; flex-wrap: wrap; }
93
+ .mpx-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;
94
+ background: color-mix(in srgb, var(--dm-text) 10%, transparent); color: var(--dm-text-muted); }
95
+ .mpx-chip.is-ok { background: color-mix(in srgb, var(--dm-success) 16%, transparent); color: var(--dm-success); }
96
+ .mpx-grid { display: grid; grid-template-columns: 1fr 1fr; gap: .8rem; }
97
+ .mpx-wide { grid-column: 1 / -1; }
98
+ .mpx .form-label { display: flex; align-items: center; gap: .3rem; }
99
+ .mpx-err { display: block; color: var(--dm-danger); font-size: var(--dm-text-xs); margin-top: .25rem; }
100
+ .mpx-hint { display: block; color: var(--dm-text-muted); font-size: var(--dm-text-xs); margin-top: .25rem; }
101
+ .mpx-hint.is-ok { color: var(--dm-success); }
102
+ .mpx-show { display: inline-flex; align-items: center; gap: .4rem; font-size: var(--dm-text-sm); color: var(--dm-text-muted); cursor: pointer; width: fit-content; }
103
+ .mpx-check { display: inline-flex; align-items: center; gap: .4rem; cursor: pointer; margin-right: .8rem; }
104
+ @media (max-width: 640px) { .mpx-grid { grid-template-columns: 1fr; } }
105
+ </style>
@@ -1,14 +1,74 @@
1
- <div class="view-container">
2
- <div class="view-header">
3
- <h1><span data-icon="bell"></span> Notifications</h1>
4
- </div>
5
-
6
- <div id="notif-list"></div>
1
+ <!-- Notifications. Bound with M.applyBindings - see admin/views/notifications.js, which also inserts the row
2
+ template (the router renders a templateUrl through _.render, eating any {{…}}). No page heading: the
3
+ Tool banner above is the heading, and Dismiss all / Refresh are banner actions. -->
4
+ <div class="ntx">
5
+ <p class="ntx-note">Messages for this site's administrators - maintenance, updates and warnings, mostly from the
6
+ manager that hosts it.
7
+ <span data-help-title="Notifications" data-help="Opening this screen marks everything in it as read - the ones that were unread stay marked New until you leave. Click a notification to read all of it; right-click it (or ⋮, or Shift+F10) to open its link, copy it, mark it unread again, dismiss it or delete it. Dismissing hides it for you only; deleting removes it for everyone and needs the delete permission. A notification with an end date disappears by itself once it passes."></span></p>
7
8
 
8
- <div id="notif-empty" hidden class="card">
9
- <div class="card-body" style="text-align:center;padding:3rem;color:var(--dm-text-muted)">
10
- <span data-icon="check-circle" style="font-size:2rem;display:block;margin-bottom:.5rem"></span>
11
- <p>No notifications - you're all caught up.</p>
9
+ <div class="ntx-card">
10
+ <div class="ntx-bar">
11
+ <div class="ntx-seg" role="group" aria-label="Show">
12
+ <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>
13
+ <button type="button" data-on-click="showNew" data-bind-class="filter.value === 'new' && 'is-on'" data-bind-hidden="!counts.value.new">New <b data-bind-text="counts.value.new"></b></button>
14
+ <button type="button" data-on-click="showCritical" data-bind-class="filter.value === 'critical' && 'is-on'" data-bind-hidden="!counts.value.critical">Critical <b data-bind-text="counts.value.critical"></b></button>
15
+ <button type="button" data-on-click="showWarning" data-bind-class="filter.value === 'warning' && 'is-on'" data-bind-hidden="!counts.value.warning">Warnings <b data-bind-text="counts.value.warning"></b></button>
16
+ <button type="button" data-on-click="showInfo" data-bind-class="filter.value === 'info' && 'is-on'" data-bind-hidden="!counts.value.info">Info <b data-bind-text="counts.value.info"></b></button>
17
+ <button type="button" data-on-click="showSuccess" data-bind-class="filter.value === 'success' && 'is-on'" data-bind-hidden="!counts.value.success">Success <b data-bind-text="counts.value.success"></b></button>
18
+ </div>
19
+ <input type="search" class="form-input ntx-search" placeholder="Search notifications…" aria-label="Search notifications" data-model="query.value">
12
20
  </div>
21
+ <p class="ntx-note" data-bind-hidden="!loading.value">Loading…</p>
22
+ <p class="ntx-note is-bad" data-bind-hidden="!loadError.value" data-bind-text="loadError.value"></p>
23
+ <div class="ntx-empty" data-bind-hidden="loading.value || loadError.value || rows.value.length">
24
+ <span data-icon="check-circle" data-icon-size="28"></span>
25
+ <span data-bind-text="emptyText.value"></span>
26
+ </div>
27
+ <div class="ntx-list" data-each="rows.value key=id"></div>
13
28
  </div>
14
29
  </div>
30
+
31
+ <style>
32
+ .ntx { display: flex; flex-direction: column; gap: .75rem; }
33
+ .ntx [hidden] { display: none !important; }
34
+ .ntx-note { margin: 0; color: var(--dm-text-muted); font-size: var(--dm-text-sm); }
35
+ .ntx-note.is-bad { color: var(--dm-danger); }
36
+ .ntx-card { border: 1px solid var(--dm-border); border-radius: var(--dm-radius-md); padding: .75rem .9rem; display: flex; flex-direction: column; gap: .6rem; }
37
+ .ntx-bar { display: flex; flex-wrap: wrap; gap: .5rem; align-items: center; }
38
+ .ntx-search { flex: 1; min-width: 10rem; max-width: 22rem; }
39
+ .ntx-seg { display: inline-flex; flex-wrap: wrap; border: 1px solid var(--dm-border); border-radius: var(--dm-radius-md); overflow: hidden; }
40
+ .ntx-seg button { border: 0; background: none; color: var(--dm-text); padding: .3rem .7rem; cursor: pointer; font: inherit; font-size: var(--dm-text-sm); }
41
+ .ntx-seg button + button { border-left: 1px solid var(--dm-border); }
42
+ .ntx-seg button b { font-weight: 500; opacity: .65; margin-left: .2rem; }
43
+ .ntx-seg button:hover { background: var(--dm-table-hover-bg, color-mix(in srgb, var(--dm-text) 6%, transparent)); }
44
+ .ntx-seg button.is-on { background: var(--dm-primary); color: var(--dm-primary-text); }
45
+ .ntx-empty { display: flex; flex-direction: column; align-items: center; gap: .5rem; padding: 2rem 1rem; color: var(--dm-text-muted); }
46
+ .ntx-empty [data-icon], .ntx-empty svg { color: var(--dm-success); }
47
+ .ntx-list { display: flex; flex-direction: column; }
48
+ .ntx-row { display: flex; align-items: flex-start; gap: .7rem; padding: .6rem .35rem; border-bottom: 1px solid var(--dm-border); cursor: pointer; }
49
+ .ntx-row:last-child { border-bottom: 0; }
50
+ .ntx-row:hover, .ntx-row:focus-visible { background: var(--dm-table-hover-bg, color-mix(in srgb, var(--dm-text) 6%, transparent)); outline: none; }
51
+ .ntx-dot { width: .55rem; height: .55rem; border-radius: 50%; margin-top: .45rem; flex-shrink: 0; background: var(--dm-info, var(--dm-primary)); }
52
+ .ntx-row.is-critical .ntx-dot { background: var(--dm-danger); }
53
+ .ntx-row.is-warning .ntx-dot { background: var(--dm-warning); }
54
+ .ntx-row.is-success .ntx-dot { background: var(--dm-success); }
55
+ .ntx-row:not(.is-unread) .ntx-main strong { font-weight: 500; }
56
+ .ntx-row.is-critical { box-shadow: inset 3px 0 0 var(--dm-danger); }
57
+ .ntx-main { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: .15rem; }
58
+ .ntx-head { display: flex; align-items: center; gap: .4rem; flex-wrap: wrap; }
59
+ .ntx-main small { color: var(--dm-text-muted); font-size: var(--dm-text-xs); }
60
+ .ntx-preview { color: var(--dm-text-muted); font-size: var(--dm-text-sm); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
61
+ .ntx-body { white-space: pre-wrap; font-size: var(--dm-text-sm); color: var(--dm-text); margin-top: .25rem; overflow-wrap: anywhere; }
62
+ .ntx-actions { display: flex; gap: .4rem; margin-top: .4rem; }
63
+ .ntx-actions .btn { display: inline-flex; align-items: center; gap: .3rem; }
64
+ .ntx-chip { display: inline-flex; align-items: center; font-size: var(--dm-text-xs); font-weight: 600; padding: .05rem .5rem; border-radius: 999px; white-space: nowrap;
65
+ background: color-mix(in srgb, var(--dm-text) 10%, transparent); color: var(--dm-text-muted); }
66
+ .ntx-chip.is-danger { background: color-mix(in srgb, var(--dm-danger) 16%, transparent); color: var(--dm-danger); }
67
+ .ntx-chip.is-warn { background: color-mix(in srgb, var(--dm-warning) 18%, transparent); color: var(--dm-warning); }
68
+ .ntx-chip.is-ok { background: color-mix(in srgb, var(--dm-success) 16%, transparent); color: var(--dm-success); }
69
+ .ntx-chip.is-info { background: color-mix(in srgb, var(--dm-info, var(--dm-primary)) 14%, transparent); color: var(--dm-info, var(--dm-primary)); }
70
+ .ntx-chip.is-new { background: var(--dm-primary); color: var(--dm-primary-text); }
71
+ .ntx-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; }
72
+ .ntx-kebab:hover { color: var(--dm-primary); }
73
+ @media (max-width: 640px) { .ntx-preview { white-space: normal; } }
74
+ </style>
@@ -1,15 +1,20 @@
1
- <div class="view-header">
2
- <h1 id="editor-title"><span data-icon="edit"></span> Page Editor</h1>
3
- <div class="view-header-actions">
4
- <select id="quick-switch" class="form-select form-select-sm" style="display:none; width:auto;">
5
- <option value="">Switch page...</option>
6
- </select>
7
- <a id="view-page-btn" class="btn btn-ghost" target="_blank" rel="noopener" style="display:none;">
8
- <span data-icon="external-link"></span> View Page
9
- </a>
10
- <button id="cancel-btn" class="btn btn-ghost">Cancel</button>
11
- <button id="save-btn" class="btn btn-primary"><span data-icon="save"></span> Save</button>
12
- </div>
1
+ <!-- Page editor. No page heading: the Tool banner above is the heading ("Pages › Edit …", set by
2
+ admin/js/views/page-editor.js), with Back, Editor reference, View on the site and Save in it. This bar
3
+ carries the page switcher and the unsaved state; Ctrl+S saves; leaving with unsaved changes asks and
4
+ keeps a draft. -->
5
+ <div class="pex-bar">
6
+ <select id="quick-switch" class="form-select form-select-sm" style="display:none; width:auto;" aria-label="Switch to another page">
7
+ <option value="">Switch page...</option>
8
+ </select>
9
+ <span data-help-title="Switching pages" data-help="Opens another page in the editor. It is locked while this page has unsaved changes - save or discard them first."></span>
10
+ <span id="pe-unsaved" class="pex-chip" hidden title="Changes not saved yet">Unsaved</span>
11
+ <small id="pe-problem" class="pex-problem" hidden></small>
12
+ <span class="pex-spacer"></span>
13
+ <a id="view-page-btn" class="btn btn-ghost btn-sm" target="_blank" rel="noopener" style="display:none;">
14
+ <span data-icon="external-link"></span> View page
15
+ </a>
16
+ <button type="button" id="discard-btn" class="btn btn-ghost btn-sm" hidden>Discard</button>
17
+ <button type="button" id="save-btn" class="btn btn-primary btn-sm"><span data-icon="save"></span> Save</button>
13
18
  </div>
14
19
 
15
20
  <div class="tabs" id="editor-meta-tabs">
@@ -55,7 +60,7 @@
55
60
  <div class="tab-panel">
56
61
  <div class="row mb-3">
57
62
  <div class="col">
58
- <label class="form-label">URL Path</label>
63
+ <label class="form-label">URL Path <span data-help-title="URL path" data-help="Where the page lives on the site, starting with /. Changing it moves the page; menu links to the old address are updated. A project whose root URL is a prefix of this address files the page under it."></span></label>
59
64
  <input id="page-url-path" type="text" class="form-input" placeholder="/about or /services/web-dev">
60
65
  <small class="form-hint">The URL where this page will live. Changing this will update any navigation links
61
66
  pointing to the old URL.</small>
@@ -67,11 +72,11 @@
67
72
  <input id="field-title" type="text" class="form-input" placeholder="Page title">
68
73
  </div>
69
74
  <div class="col-3">
70
- <label class="form-label">Layout</label>
75
+ <label class="form-label">Layout <span data-help="The page shell: navbar, width, sidebar, footer. Layouts are managed under System › Layouts."></span></label>
71
76
  <select id="field-layout" class="form-select"></select>
72
77
  </div>
73
78
  <div class="col-3">
74
- <label class="form-label">Status</label>
79
+ <label class="form-label">Status <span data-help-title="Draft or published" data-help="A draft is hidden from visitors (404) - but anyone who can read pages sees it with a banner, and Live Preview and share links show it before it is published."></span></label>
75
80
  <select id="field-status" class="form-select">
76
81
  <option value="draft">Draft</option>
77
82
  <option value="published">Published</option>
@@ -84,24 +89,24 @@
84
89
  <input id="field-description" type="text" class="form-input" placeholder="Short description">
85
90
  </div>
86
91
  <div class="col-4">
87
- <label class="form-label">Sort Order</label>
92
+ <label class="form-label">Sort Order <span data-help="Lower numbers come first in navigation and in page lists sorted by navigation order."></span></label>
88
93
  <input id="field-sort-order" type="number" class="form-input" value="99" min="0">
89
94
  </div>
90
95
  </div>
91
96
  <div class="row mb-3">
92
97
  <div class="col-4">
93
- <label class="form-label">Category</label>
98
+ <label class="form-label">Category <span data-help="A free label for grouping pages - collection displays and search can filter by it."></span></label>
94
99
  <input id="field-category" type="text" class="form-input" placeholder="e.g. news, docs, products">
95
100
  </div>
96
101
  <div class="col-4">
97
- <label class="form-label">Visibility</label>
102
+ <label class="form-label">Visibility <span data-help-title="Who can see the page" data-help="Public: everyone. A role &quot;and above&quot;: that role and every more senior one. A role &quot;only&quot;: holders of that role (and the super-admin). Private: the super-admin alone. Signed-out visitors are sent to sign in."></span></label>
98
103
  <select id="field-visibility" class="form-select">
99
104
  <option value="public">Public - everyone</option>
100
105
  <option value="private">Private - super-admin only</option>
101
106
  </select>
102
107
  </div>
103
108
  <div class="col-4">
104
- <label class="form-label">Project</label>
109
+ <label class="form-label">Project <span data-help="Which project the page belongs to. Inherit from URL files it under the project whose root URL matches its address, else Core."></span></label>
105
110
  <select id="field-project" class="form-select">
106
111
  <option value="">Inherit from URL</option>
107
112
  </select>
@@ -110,14 +115,14 @@
110
115
  </div>
111
116
  <div class="row mb-3">
112
117
  <div class="col-6">
113
- <label class="form-label">Theme Override</label>
118
+ <label class="form-label">Theme Override <span data-help="Shows this page in a different theme from the rest of the site - built-in or custom."></span></label>
114
119
  <select id="field-theme" class="form-select"></select>
115
120
  <small class="form-hint">Overrides the site-wide theme for this page only.</small>
116
121
  </div>
117
122
  </div>
118
123
  <div class="row mb-3">
119
124
  <div class="col">
120
- <label class="form-label">Tags</label>
125
+ <label class="form-label">Tags <span data-help="Keywords for filtering and search. Type and press Enter; existing tags are suggested."></span></label>
121
126
  <input id="field-tags" type="text" class="form-input" />
122
127
  <small class="form-hint">Comma-separated keywords for filtering and search.</small>
123
128
  </div>
@@ -142,6 +147,7 @@
142
147
  <label class="form-check-label" title="Included in fresh installs via the seed script">
143
148
  <input id="field-bundled" type="checkbox" class="form-check"> Bundled
144
149
  </label>
150
+ <span data-help="Included when a fresh site is seeded. Leave off for pages that belong to this site only."></span>
145
151
  </div>
146
152
  </div>
147
153
  </div>
@@ -150,13 +156,13 @@
150
156
  <div class="tab-panel">
151
157
  <div class="row mb-3">
152
158
  <div class="col">
153
- <label class="form-label">SEO Title</label>
159
+ <label class="form-label">SEO Title <span data-help="The title search engines and browser tabs show. Blank uses the page title."></span></label>
154
160
  <input id="field-seo-title" type="text" class="form-input" placeholder="Overrides default title">
155
161
  </div>
156
162
  </div>
157
163
  <div class="row">
158
164
  <div class="col">
159
- <label class="form-label">SEO Description</label>
165
+ <label class="form-label">SEO Description <span data-help="The summary search engines show under the title. Blank uses the page description."></span></label>
160
166
  <input id="field-seo-desc" type="text" class="form-input" placeholder="Overrides default description">
161
167
  </div>
162
168
  </div>
@@ -164,7 +170,7 @@
164
170
 
165
171
  <!-- DConfig -->
166
172
  <div class="tab-panel">
167
- <label class="form-label">Config JSON</label>
173
+ <label class="form-label">Config JSON <span data-help-title="DConfig" data-help="Declarative behaviour for elements on this page - click handlers, class toggles - as a JSON object keyed by CSS selector. Must be valid JSON before the page can be saved."></span></label>
168
174
  <textarea id="field-dconfig" class="form-input dconfig-textarea"
169
175
  placeholder='{ "#my-btn": { "events": { "click": { "target": "#panel", "toggleClass": "hidden" } } } }'></textarea>
170
176
  <small class="form-hint">Declarative click handlers and class toggles applied on page load. Merged with any <code>[dconfig]</code>
@@ -186,6 +192,7 @@
186
192
  </button>
187
193
  </div>
188
194
  <span class="live-preview-status" id="live-preview-status">Showing unsaved changes</span>
195
+ <span data-help-title="Live Preview" data-help="The whole page - navbar, footer, theme - rendered by the server from what is in the editor now, before saving. Share link makes a private, revocable address for someone without an account."></span>
189
196
  <div class="live-preview-actions">
190
197
  <button type="button" class="btn btn-ghost btn-sm" id="live-preview-refresh">
191
198
  <span data-icon="refresh-cw"></span> Refresh
@@ -204,10 +211,10 @@
204
211
  <!-- History -->
205
212
  <div class="tab-panel" id="history-panel">
206
213
  <div class="mb-3" style="display:flex;align-items:center;justify-content:space-between;">
207
- <h3 style="margin:0;font-size:1rem;">Version History</h3>
214
+ <h3 style="margin:0;font-size:1rem;">Version History <span data-help="A version is kept on every save. Click one to compare it with the editor; right-click it (or ⋮, or Shift+F10) to restore or delete it. Restoring keeps a snapshot of the current page first."></span></h3>
208
215
  <div style="display:flex;gap:.5rem;align-items:center;">
209
216
  <button class="btn btn-ghost btn-sm" id="history-delete-selected-btn"
210
- style="display:none;color:var(--dm-danger,#e55);">
217
+ style="display:none;color:var(--dm-danger);">
211
218
  <span data-icon="trash"></span> Delete Selected
212
219
  </button>
213
220
  <button class="btn btn-ghost btn-sm" id="history-save-named-btn">
@@ -247,3 +254,17 @@
247
254
  </div>
248
255
 
249
256
  <div id="media-picker-modal"></div>
257
+
258
+ <style>
259
+ .pex-bar { display: flex; flex-wrap: wrap; align-items: center; gap: .5rem; margin-bottom: .75rem; }
260
+ .pex-bar .btn { display: inline-flex; align-items: center; gap: .3rem; }
261
+ .pex-spacer { flex: 1; }
262
+ .pex-chip { display: inline-flex; align-items: center; font-size: var(--dm-text-xs); font-weight: 600; padding: .1rem .5rem; border-radius: 999px;
263
+ background: color-mix(in srgb, var(--dm-warning) 18%, transparent); color: var(--dm-warning); }
264
+ .pex-problem { color: var(--dm-danger); font-size: var(--dm-text-sm); }
265
+ .pex-bar [hidden] { display: none !important; }
266
+ #history-panel .pe-version-row { cursor: pointer; }
267
+ #history-panel .pe-version-row:hover, #history-panel .pe-version-row:focus-visible { background: var(--dm-table-hover-bg, color-mix(in srgb, var(--dm-text) 6%, transparent)); outline: none; }
268
+ .pe-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; }
269
+ .pe-kebab:hover { color: var(--dm-primary); }
270
+ </style>