domma-cms 0.76.0 → 0.77.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) hide show
  1. package/CLAUDE.md +1 -1
  2. package/admin/js/api.js +1 -1
  3. package/admin/js/app.js +2 -2
  4. package/admin/js/lib/action-editor-arrange.js +1 -0
  5. package/admin/js/lib/api-endpoint-editor-arrange.js +2 -0
  6. package/admin/js/lib/block-editor-arrange.js +2 -0
  7. package/admin/js/lib/collection-editor-arrange.js +3 -0
  8. package/admin/js/lib/collection-entries-arrange.js +1 -0
  9. package/admin/js/lib/component-editor-arrange.js +27 -0
  10. package/admin/js/lib/context-menu-editor-arrange.js +1 -0
  11. package/admin/js/lib/dashboard-arrange.js +1 -0
  12. package/admin/js/lib/form-editor-arrange.js +1 -0
  13. package/admin/js/lib/form-submissions-arrange.js +2 -0
  14. package/admin/js/lib/menu-editor-arrange.js +2 -0
  15. package/admin/js/lib/page-editor-arrange.js +1 -0
  16. package/admin/js/lib/plugin-code-arrange.js +1 -0
  17. package/admin/js/lib/project-page-arrange.js +1 -0
  18. package/admin/js/lib/role-editor-arrange.js +3 -0
  19. package/admin/js/lib/user-editor-arrange.js +1 -0
  20. package/admin/js/lib/view-editor-arrange.js +14 -0
  21. package/admin/js/templates/action-editor.html +223 -147
  22. package/admin/js/templates/api-endpoint-editor.html +172 -117
  23. package/admin/js/templates/block-editor.html +194 -203
  24. package/admin/js/templates/collection-editor.html +191 -113
  25. package/admin/js/templates/collection-entries.html +99 -36
  26. package/admin/js/templates/component-editor.html +128 -126
  27. package/admin/js/templates/context-menu-editor.html +208 -148
  28. package/admin/js/templates/dashboard.html +184 -22
  29. package/admin/js/templates/form-editor.html +41 -26
  30. package/admin/js/templates/form-submissions.html +78 -26
  31. package/admin/js/templates/menu-editor.html +57 -17
  32. package/admin/js/templates/menu-locations.html +54 -11
  33. package/admin/js/templates/page-editor.html +47 -26
  34. package/admin/js/templates/plugin-code.html +91 -73
  35. package/admin/js/templates/project-detail.html +83 -145
  36. package/admin/js/templates/project-settings.html +88 -53
  37. package/admin/js/templates/role-editor.html +158 -83
  38. package/admin/js/templates/user-editor.html +150 -14
  39. package/admin/js/templates/view-editor.html +203 -180
  40. package/admin/js/templates/view-preview.html +45 -45
  41. package/admin/js/views/action-editor.js +7 -1
  42. package/admin/js/views/api-endpoint-editor.js +2 -3
  43. package/admin/js/views/block-editor.js +14 -8
  44. package/admin/js/views/collection-editor.js +64 -5
  45. package/admin/js/views/collection-entries.js +50 -1
  46. package/admin/js/views/component-editor.js +2 -28
  47. package/admin/js/views/context-menu-editor.js +54 -55
  48. package/admin/js/views/dashboard.js +7 -1
  49. package/admin/js/views/form-editor.js +7 -7
  50. package/admin/js/views/form-submissions.js +11 -1
  51. package/admin/js/views/index.js +1 -1
  52. package/admin/js/views/login.js +2 -2
  53. package/admin/js/views/menu-editor.js +21 -43
  54. package/admin/js/views/menu-locations.js +16 -1
  55. package/admin/js/views/page-editor.js +26 -26
  56. package/admin/js/views/plugin-code.js +23 -4
  57. package/admin/js/views/project-detail.js +9 -1
  58. package/admin/js/views/project-settings.js +1 -1
  59. package/admin/js/views/role-editor.js +11 -3
  60. package/admin/js/views/user-editor.js +1 -1
  61. package/admin/js/views/view-editor.js +1 -1
  62. package/admin/js/views/view-preview.js +1 -1
  63. package/package.json +1 -1
  64. package/server/routes/api/dashboard.js +32 -4
  65. package/server/routes/api/forms.js +104 -13
  66. package/server/routes/api/users.js +12 -2
  67. package/server/routes/api/views.js +55 -1
  68. package/server/services/components.js +9 -0
  69. package/server/services/users.js +2 -2
  70. package/server/services/views.js +14 -0
  71. package/admin/js/templates/dashboard/activity-feed.html +0 -3
  72. package/admin/js/templates/dashboard/cache.html +0 -32
  73. package/admin/js/templates/dashboard/health-detail.html +0 -2
  74. package/admin/js/templates/dashboard/journeys.html +0 -17
  75. package/admin/js/templates/dashboard/kpi-strip.html +0 -34
  76. package/admin/js/templates/dashboard/spike-feed.html +0 -3
  77. package/admin/js/templates/dashboard/top-pages.html +0 -3
  78. package/admin/js/templates/dashboard/traffic-chart.html +0 -3
  79. package/admin/js/views/block-editor-enhance.js +0 -1
  80. package/admin/js/views/dashboard/lib/escape.js +0 -1
  81. package/admin/js/views/dashboard/widgets/activity-feed.js +0 -1
  82. package/admin/js/views/dashboard/widgets/cache.js +0 -1
  83. package/admin/js/views/dashboard/widgets/health-detail.js +0 -1
  84. package/admin/js/views/dashboard/widgets/journeys.js +0 -1
  85. package/admin/js/views/dashboard/widgets/kpi-strip.js +0 -1
  86. package/admin/js/views/dashboard/widgets/spike-feed.js +0 -6
  87. package/admin/js/views/dashboard/widgets/top-pages.js +0 -1
  88. package/admin/js/views/dashboard/widgets/traffic-chart.js +0 -1
@@ -1,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>
@@ -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,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>
@@ -1,82 +1,100 @@
1
- <style>
2
- .pcode-layout { display: flex; gap: 1rem; align-items: stretch; min-height: 70vh; }
3
- .pcode-files { flex: 0 0 250px; overflow-y: auto; }
4
- .pcode-files .card-body { padding: .5rem; }
5
- .pcode-dir {
6
- padding: .5rem .5rem .2rem; font-size: .7rem; font-weight: 700;
7
- text-transform: uppercase; letter-spacing: .04em;
8
- color: var(--dm-text-muted, #6b7280);
9
- }
10
- .pcode-file {
11
- display: flex; align-items: center; gap: .4rem; width: 100%;
12
- padding: .3rem .5rem .3rem 1rem; border: 0; background: none; cursor: pointer;
13
- border-radius: var(--dm-radius-md, 6px); text-align: left;
14
- color: var(--dm-text, inherit); font-size: .82rem;
15
- font-family: var(--dm-font-mono, ui-monospace, Menlo, Consolas, monospace);
16
- white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
17
- }
18
- .pcode-file:hover { background: var(--dm-hover-bg, rgba(128,128,128,.12)); }
19
- .pcode-file.active { background: var(--dm-primary, #2563eb); color: #fff; }
20
- .pcode-file .pcode-dirty { margin-left: auto; font-weight: 700; }
21
- .pcode-editor-card { flex: 1; display: flex; flex-direction: column; min-width: 0; }
22
- .pcode-editor-card .card-body { flex: 1; display: flex; flex-direction: column; padding: 0; }
23
- .pcode-toolbar {
24
- display: flex; align-items: center; gap: .5rem;
25
- padding: .5rem .75rem; border-bottom: 1px solid var(--dm-border, rgba(128,128,128,.25));
26
- }
27
- .pcode-current {
28
- font-family: var(--dm-font-mono, ui-monospace, Menlo, Consolas, monospace);
29
- font-size: .85rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
30
- }
31
- #pcode-editor { flex: 1; display: flex; min-height: 55vh; }
32
- #pcode-editor > .dm-edit-wrap { flex: 1; }
33
- .pcode-statusbar {
34
- display: flex; align-items: center; gap: .75rem; padding: .4rem .75rem;
35
- border-top: 1px solid var(--dm-border, rgba(128,128,128,.25));
36
- font-size: .78rem; color: var(--dm-text-muted, #6b7280);
37
- }
38
- #pcode-cursor { margin-left: auto; font-family: var(--dm-font-mono, ui-monospace, Menlo, Consolas, monospace); }
39
- </style>
40
-
41
- <div class="view-header d-flex justify-content-between align-items-center mb-4">
42
- <div>
43
- <h1 class="view-title"><span data-icon="code"></span> <span id="pcode-title">Plugin Code</span></h1>
44
- <p class="view-subtitle text-muted" style="margin:0;font-size:.875rem">
45
- <code>admin/</code> files apply on a hard refresh; everything else needs a server restart.
46
- </p>
47
- </div>
48
- <div class="d-flex gap-2">
49
- <button id="pcode-back" class="btn btn-ghost"><span data-icon="arrow-left"></span> Plugins</button>
50
- <button id="pcode-restart" class="btn btn-warning"><span data-icon="refresh-cw"></span> Restart Server</button>
51
- </div>
52
- </div>
1
+ <!-- Plugin code editor. Bound with M.applyBindings - see admin/js/views/plugin-code.js, which also inserts
2
+ the file-row template (the router renders a templateUrl through _.render, eating any {{…}}).
3
+ No page heading: the Tool banner ("Marketplace › <plugin>") is the heading; Back, New file, Save all
4
+ and Restart server are banner actions. -->
5
+ <div class="pcx">
6
+ <p class="pcx-alert is-bad" data-bind-hidden="!loadError.value" data-bind-text="loadError.value"></p>
7
+ <p class="pcx-alert" data-bind-hidden="!restartPending.value">
8
+ <span data-icon="alert-triangle" data-icon-size="16"></span>
9
+ <span>Server files were saved - they apply when the server restarts.</span>
10
+ <button type="button" class="btn btn-sm btn-warning" data-on-click="restart">Restart server</button>
11
+ </p>
53
12
 
54
- <div class="pcode-layout">
55
- <div class="card pcode-files">
56
- <div class="card-body">
57
- <div style="display:flex;justify-content:space-between;align-items:center;padding:.25rem .5rem .5rem;">
58
- <strong style="font-size:.8rem">Files</strong>
59
- <button id="pcode-newfile" class="btn btn-sm btn-ghost" title="New file"><span data-icon="plus"></span></button>
13
+ <div class="pcx-layout">
14
+ <div class="pcx-card pcx-files">
15
+ <div class="pcx-card-title">Files
16
+ <span class="pcx-count" data-bind-text="files.value.length"></span>
17
+ <span data-help-title="Files" data-help="Every editable file in the plugin (.js, .json, .md, .html, .css, .txt, .svg). Click one to open it - unsaved edits are kept per file while you switch, and a ● marks them. Right-click a file (or ⋮, or Shift+F10) to save, revert, copy its path, rename, duplicate or delete it. plugin.json, plugin.js and config.js cannot be deleted or renamed."></span>
18
+ <span class="pcx-spacer"></span>
19
+ <button type="button" class="btn btn-sm btn-ghost" data-on-click="newFile" title="New file" aria-label="New file"><span data-icon="plus" data-icon-size="14"></span></button>
60
20
  </div>
61
- <div id="pcode-filelist"></div>
21
+ <input type="search" class="form-input pcx-search" placeholder="Filter files…" aria-label="Filter files" data-model="query.value">
22
+ <p class="pcx-note" data-bind-hidden="!loading.value">Loading…</p>
23
+ <p class="pcx-note" data-bind-hidden="loading.value || rows.value.length" data-bind-text="emptyText.value"></p>
24
+ <div class="pcx-list" data-each="rows.value key=key"></div>
62
25
  </div>
63
- </div>
64
26
 
65
- <div class="card pcode-editor-card">
66
- <div class="card-body">
67
- <div class="pcode-toolbar">
68
- <span class="pcode-current" id="pcode-current">Select a file</span>
69
- <span style="flex:1"></span>
70
- <button id="pcode-rename" class="btn btn-sm btn-ghost" title="Rename file" disabled><span data-icon="edit-3"></span></button>
71
- <button id="pcode-delete" class="btn btn-sm btn-ghost" title="Delete file" disabled><span data-icon="trash"></span></button>
72
- <button id="pcode-saveall" class="btn btn-sm btn-ghost" disabled>Save All</button>
73
- <button id="pcode-save" class="btn btn-sm btn-primary" disabled><span data-icon="save"></span> Save</button>
27
+ <div class="pcx-card pcx-editor-card">
28
+ <div class="pcx-toolbar">
29
+ <span class="pcx-current" data-bind-text="currentText.value">Select a file</span>
30
+ <span class="pcx-chip" data-bind-hidden="!openPath.value" data-bind-class="scopeCls.value" data-bind-text="scopeLabel.value" data-bind-title="scopeHint.value"></span>
31
+ <span class="pcx-chip is-warn" data-bind-hidden="!currentDirty.value">Unsaved</span>
32
+ <span data-help-title="Saving" data-help="Ctrl+S saves the open file, Ctrl+Shift+S every unsaved file. An admin/ file applies when the admin tab is refreshed; anything else (plugin.js, config.js, public snippets…) is read when the server starts, so it needs a restart - Restart server is in the banner. Leaving with unsaved files asks first and keeps them as a draft for your next visit."></span>
33
+ <span class="pcx-spacer"></span>
34
+ <button type="button" class="btn btn-sm btn-ghost" data-on-click="revert" data-bind-hidden="!currentDirty.value" title="Put the open file back as it was saved">Revert</button>
35
+ <button type="button" class="btn btn-sm btn-primary" data-on-click="save" data-bind-disabled="!currentDirty.value || saving.value">
36
+ <span data-icon="save" data-icon-size="14"></span> Save</button>
37
+ <button type="button" class="pcx-kebab pcx-more" data-bind-hidden="!openPath.value" aria-label="More for this file" title="More">⋮</button>
74
38
  </div>
75
- <div id="pcode-editor"></div>
76
- <div class="pcode-statusbar">
77
- <span id="pcode-status">Ctrl+S saves the open file. Unsaved edits survive switching files.</span>
78
- <span id="pcode-cursor"></span>
39
+ <div class="pcx-editor" id="pcode-editor"></div>
40
+ <p class="pcx-empty" data-bind-hidden="openPath.value">Choose a file on the left to edit it.</p>
41
+ <div class="pcx-statusbar">
42
+ <span data-bind-text="status.value"></span>
43
+ <span class="pcx-spacer"></span>
44
+ <span data-bind-hidden="!dirtyCount.value" data-bind-text="dirtyText.value"></span>
45
+ <span class="pcx-cursor" data-bind-text="cursor.value"></span>
79
46
  </div>
80
47
  </div>
81
48
  </div>
82
49
  </div>
50
+
51
+ <style>
52
+ .pcx { display: flex; flex-direction: column; gap: .75rem; }
53
+ .pcx [hidden], .pcx-form [hidden] { display: none !important; }
54
+ .pcx-alert { margin: 0; display: flex; gap: .5rem; align-items: center; flex-wrap: wrap; padding: .55rem .8rem; border-radius: var(--dm-radius-md);
55
+ background: color-mix(in srgb, var(--dm-warning) 16%, transparent); color: var(--dm-text); }
56
+ .pcx-alert.is-bad { background: color-mix(in srgb, var(--dm-danger) 14%, transparent); color: var(--dm-danger); }
57
+ .pcx-layout { display: flex; gap: .75rem; align-items: stretch; min-height: 70vh; }
58
+ .pcx-card { border: 1px solid var(--dm-border); border-radius: var(--dm-radius-md); display: flex; flex-direction: column; min-width: 0; }
59
+ .pcx-files { flex: 0 0 270px; padding: .6rem; gap: .5rem; overflow-y: auto; max-height: 80vh; }
60
+ .pcx-card-title { display: flex; align-items: center; gap: .3rem; font-weight: 600; }
61
+ .pcx-count { opacity: .6; font-size: var(--dm-text-xs); font-weight: 400; }
62
+ .pcx-spacer { flex: 1; }
63
+ .pcx-search { width: 100%; }
64
+ .pcx-note { margin: 0; color: var(--dm-text-muted); font-size: var(--dm-text-sm); }
65
+ .pcx-list { display: flex; flex-direction: column; }
66
+ .pcx-dir { padding: .5rem .35rem .15rem; font-size: var(--dm-text-xs); font-weight: 700; letter-spacing: .03em; color: var(--dm-text-muted);
67
+ font-family: var(--dm-font-mono, monospace); }
68
+ .pcx-file { display: flex; align-items: center; gap: .35rem; padding: .28rem .35rem; border-radius: var(--dm-radius-sm); cursor: pointer;
69
+ color: var(--dm-text); font-size: var(--dm-text-sm); font-family: var(--dm-font-mono, monospace); min-width: 0; }
70
+ .pcx-file.is-nested { padding-left: 1rem; }
71
+ .pcx-file:hover, .pcx-file:focus-visible { background: var(--dm-table-hover-bg, color-mix(in srgb, var(--dm-text) 6%, transparent)); outline: none; }
72
+ .pcx-file.is-open { background: color-mix(in srgb, var(--dm-primary) 16%, transparent); box-shadow: inset 3px 0 0 var(--dm-primary); }
73
+ .pcx-name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
74
+ .pcx-dirty { color: var(--dm-warning); font-weight: 700; }
75
+ .pcx-kebab { border: 0; background: none; cursor: pointer; font-size: var(--dm-text-lg); line-height: 1; color: var(--dm-text-muted);
76
+ border-radius: var(--dm-radius-sm); padding: 0 .3rem; }
77
+ .pcx-kebab:hover { color: var(--dm-primary); }
78
+ .pcx-editor-card { flex: 1; }
79
+ .pcx-toolbar { display: flex; align-items: center; gap: .45rem; flex-wrap: wrap; padding: .5rem .75rem; border-bottom: 1px solid var(--dm-border); }
80
+ .pcx-toolbar .btn { display: inline-flex; align-items: center; gap: .3rem; }
81
+ .pcx-current { font-family: var(--dm-font-mono, monospace); font-size: var(--dm-text-sm); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
82
+ .pcx-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;
83
+ background: color-mix(in srgb, var(--dm-text) 10%, transparent); color: var(--dm-text-muted); }
84
+ .pcx-chip.is-info { background: color-mix(in srgb, var(--dm-info, var(--dm-primary)) 14%, transparent); color: var(--dm-info, var(--dm-primary)); }
85
+ .pcx-chip.is-warn { background: color-mix(in srgb, var(--dm-warning) 18%, transparent); color: var(--dm-warning); }
86
+ .pcx-editor { flex: 1; display: flex; min-height: 55vh; }
87
+ .pcx-editor > .dm-edit-wrap { flex: 1; }
88
+ .pcx-empty { margin: 0; padding: 2rem; color: var(--dm-text-muted); text-align: center; }
89
+ .pcx-statusbar { display: flex; align-items: center; gap: .75rem; padding: .4rem .75rem; border-top: 1px solid var(--dm-border);
90
+ font-size: var(--dm-text-xs); color: var(--dm-text-muted); }
91
+ .pcx-cursor { font-family: var(--dm-font-mono, monospace); }
92
+ /* The path slideover */
93
+ .pcx-form { display: flex; flex-direction: column; gap: .85rem; }
94
+ .pcx-form .form-label { display: flex; align-items: center; gap: .3rem; }
95
+ .pcx-err { display: block; color: var(--dm-danger); font-size: var(--dm-text-xs); margin-top: .25rem; }
96
+ .pcx-hint { display: block; color: var(--dm-text-muted); font-size: var(--dm-text-xs); margin-top: .25rem; }
97
+ .pcx-actions { display: flex; align-items: center; gap: .4rem; padding-top: .6rem; border-top: 1px solid var(--dm-border); }
98
+ .pcx-actions .btn { display: inline-flex; align-items: center; gap: .3rem; }
99
+ @media (max-width: 800px) { .pcx-layout { flex-direction: column; } .pcx-files { flex-basis: auto; max-height: 40vh; } }
100
+ </style>