domma-cms 0.75.0 → 0.76.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 (66) hide show
  1. package/admin/js/app.js +4 -4
  2. package/admin/js/lib/api-tokens-arrange.js +3 -0
  3. package/admin/js/lib/effects-arrange.js +3 -0
  4. package/admin/js/lib/layouts-arrange.js +1 -0
  5. package/admin/js/lib/my-profile-arrange.js +1 -0
  6. package/admin/js/lib/notifications-arrange.js +1 -0
  7. package/admin/js/lib/plugins-arrange.js +2 -0
  8. package/admin/js/lib/roles-arrange.js +1 -0
  9. package/admin/js/lib/search-settings-arrange.js +1 -0
  10. package/admin/js/lib/sidebar-renderer.js +2 -2
  11. package/admin/js/lib/site-settings-arrange.js +1 -0
  12. package/admin/js/lib/users-arrange.js +1 -0
  13. package/admin/js/templates/api-tokens.html +83 -10
  14. package/admin/js/templates/effects.html +155 -72
  15. package/admin/js/templates/layouts.html +126 -43
  16. package/admin/js/templates/my-profile.html +100 -12
  17. package/admin/js/templates/notifications.html +70 -10
  18. package/admin/js/templates/plugins.html +100 -28
  19. package/admin/js/templates/roles.html +78 -7
  20. package/admin/js/templates/search.html +193 -216
  21. package/admin/js/templates/settings.html +404 -472
  22. package/admin/js/templates/users.html +69 -9
  23. package/admin/js/views/api-tokens.js +62 -8
  24. package/admin/js/views/effects.js +31 -1
  25. package/admin/js/views/layouts.js +81 -1
  26. package/admin/js/views/my-profile.js +1 -1
  27. package/admin/js/views/notifications.js +18 -1
  28. package/admin/js/views/plugins.js +97 -39
  29. package/admin/js/views/roles.js +49 -4
  30. package/admin/js/views/search.js +7 -1
  31. package/admin/js/views/settings.js +13 -3
  32. package/admin/js/views/users.js +13 -4
  33. package/package.json +1 -1
  34. package/plugins/_lib/admin/mail/compose-window.js +97 -40
  35. package/plugins/_lib/admin/mail/contacts.js +4 -1
  36. package/plugins/_lib/admin/mail/diagnostics-section.js +4 -1
  37. package/plugins/_lib/admin/mail/folder-tree.js +1 -1
  38. package/plugins/_lib/admin/mail/image-senders-section.js +69 -90
  39. package/plugins/_lib/admin/mail/mail.css +1 -1
  40. package/plugins/_lib/admin/mail/mail.html +1 -1
  41. package/plugins/_lib/admin/mail/reader-view.js +303 -44
  42. package/plugins/_lib/admin/mail/when-closed.js +26 -0
  43. package/plugins/_lib/mail/readRoutes.js +43 -1
  44. package/plugins/_lib/mail/unreadBadge.js +42 -0
  45. package/plugins/free-tier.lock.json +5 -5
  46. package/plugins/mail-reader/CLAUDE.md +1 -0
  47. package/plugins/mail-reader/plugin.js +3 -1
  48. package/plugins/mail-reader/plugin.json +5 -4
  49. package/server/routes/api/api-tokens.js +56 -0
  50. package/server/routes/api/auth.js +7 -1
  51. package/server/routes/api/collections.js +13 -1
  52. package/server/routes/api/effects.js +68 -13
  53. package/server/routes/api/layouts.js +93 -2
  54. package/server/routes/api/notifications.js +140 -14
  55. package/server/routes/api/plugins.js +135 -4
  56. package/server/routes/api/roles.js +111 -0
  57. package/server/routes/api/search.js +38 -4
  58. package/server/routes/api/settings.js +52 -0
  59. package/server/routes/api/users.js +53 -1
  60. package/server/server.js +12 -0
  61. package/server/services/plugins.js +7 -0
  62. package/server/services/roles.js +3 -0
  63. package/server/services/search.js +58 -0
  64. package/server/services/users.js +14 -0
  65. package/admin/js/templates/plugin-marketplace.html +0 -29
  66. package/admin/js/views/plugin-marketplace.js +0 -1
@@ -1,241 +1,218 @@
1
- <div class="view-header">
2
- <h1><span data-icon="search"></span> Search</h1>
3
- <div>
4
- <button id="save-settings-btn" class="btn btn-primary">
5
- <span data-icon="save"></span> Save
6
- </button>
7
- </div>
8
- </div>
9
-
10
- <div id="ss-settings-tabs" class="tabs">
11
- <div class="tab-list">
12
- <button class="tab-item active">Settings</button>
13
- <button class="tab-item">Trigger &amp; Position</button>
14
- <button class="tab-item">How it works</button>
15
- </div>
16
- <div class="tab-content">
17
-
18
- <!-- Settings Tab -->
19
- <div class="tab-panel active" id="tab-search-settings">
20
- <div class="card mb-4" style="margin-top:1rem;">
21
- <div class="card-body">
1
+ <!-- System › Search. Bound with M.applyBindings - see admin/js/views/search.js, which also inserts the
2
+ preview row template (the router renders a templateUrl through _.render, eating any {{…}}).
3
+ No page heading: the Tool banner above is the heading; Save and View site are banner actions. -->
4
+ <div class="ssx">
5
+ <p class="ssx-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="ssx-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>
11
+ <p class="ssx-alert" data-bind-hidden="!clampText.value">
12
+ <span data-icon="alert-triangle" data-icon-size="16"></span>
13
+ <span data-bind-text="clampText.value"></span></p>
14
+
15
+ <div class="tabs ssx-tabs">
16
+ <div class="tab-list">
17
+ <button type="button" class="tab-item active">Search</button>
18
+ <button type="button" class="tab-item">Trigger &amp; position</button>
19
+ <button type="button" class="tab-item">Try it</button>
20
+ <button type="button" class="tab-item">How it works</button>
21
+ </div>
22
22
 
23
- <div class="row mb-3">
24
- <div class="col">
25
- <label class="form-check-label">
26
- <input id="field-enabled" type="checkbox">
27
- Enable search on the public site
28
- </label>
29
- <span class="form-hint">When off, no search trigger, stylesheet or script is added to public pages.</span>
30
- </div>
31
- </div>
23
+ <!-- Search -->
24
+ <div class="tab-panel active">
25
+ <div class="ssx-card">
26
+ <div class="ssx-card-title">Live now
27
+ <span data-help-title="Live now" data-help="What visitors get at the moment - the saved settings, not what you are editing. Searchable pages are the published, public ones; drafts and private pages never appear in results."></span></div>
28
+ <div class="ssx-chips" data-each="chips.value key=text"></div>
29
+ </div>
32
30
 
33
- <div class="row mb-3">
34
- <div class="col">
35
- <label class="form-label">Search placeholder text</label>
36
- <input id="field-placeholder" type="text" class="form-input" placeholder="Search pages...">
37
- <span class="form-hint">Displayed inside the search input when empty.</span>
38
- </div>
31
+ <div class="ssx-card">
32
+ <div class="ssx-card-title">Search on the site</div>
33
+ <div class="ssx-setting">
34
+ <label class="form-label" for="ssx-enabled">Enable search on the public site
35
+ <span data-help="Off removes search completely - no trigger, stylesheet or script is added to public pages. Context menus offering Site search do nothing while it is off."></span></label>
36
+ <input id="ssx-enabled" type="checkbox" class="form-switch" data-model="enabled.value">
37
+ </div>
38
+ <div class="ssx-grid">
39
+ <div class="ssx-wide">
40
+ <label class="form-label" for="ssx-placeholder">Placeholder text
41
+ <span data-help="Shown inside the search box while it is empty. Up to 120 characters."></span></label>
42
+ <input id="ssx-placeholder" type="text" class="form-input" maxlength="120" placeholder="Search pages..." data-model="placeholder.value">
39
43
  </div>
44
+ </div>
45
+ <div class="ssx-setting">
46
+ <label class="form-label" for="ssx-shortcut">Keyboard shortcut (Ctrl+K / ⌘K)
47
+ <span data-help="Lets visitors open search from anywhere on a page with Ctrl+K (⌘K on a Mac), as well as by clicking the trigger."></span></label>
48
+ <input id="ssx-shortcut" type="checkbox" class="form-switch" data-model="keyboardShortcut.value">
49
+ </div>
50
+ </div>
40
51
 
41
- <div class="row mb-3">
42
- <div class="col">
43
- <label class="form-check-label">
44
- <input id="field-keyboard-shortcut" type="checkbox">
45
- Enable keyboard shortcut (Ctrl+K / ⌘K)
46
- </label>
47
- <span class="form-hint">Allows visitors to open the search overlay using a keyboard shortcut.</span>
48
- </div>
52
+ <div class="ssx-card">
53
+ <div class="ssx-card-title">Results</div>
54
+ <div class="ssx-grid ssx-grid-3">
55
+ <div>
56
+ <label class="form-label" for="ssx-max">Max results
57
+ <span data-help="The most results one search shows (1-50). They are ranked: a title match counts most, then tags, the description, and the page's text."></span></label>
58
+ <input id="ssx-max" type="number" class="form-input" min="1" max="50" data-model="maxResults.value">
49
59
  </div>
50
-
51
- <div class="row mb-3">
52
- <div class="col-6">
53
- <label class="form-label">Max results</label>
54
- <input id="field-max-results" type="number" class="form-input" min="1" max="50"
55
- placeholder="10">
56
- <span class="form-hint">Maximum number of results to return per search (1-50).</span>
57
- </div>
58
- <div class="col-6">
59
- <label class="form-label">Minimum query length</label>
60
- <input id="field-min-query-length" type="number" class="form-input" min="1" max="5"
61
- placeholder="2">
62
- <span class="form-hint">Minimum characters required before searching (1-5).</span>
63
- </div>
60
+ <div>
61
+ <label class="form-label" for="ssx-min">Minimum query length
62
+ <span data-help="How many characters a visitor types before searching starts (1-5). 1 searches on every keystroke; 2 or 3 avoids a flood of weak matches."></span></label>
63
+ <input id="ssx-min" type="number" class="form-input" min="1" max="5" data-model="minQueryLength.value">
64
64
  </div>
65
-
66
- <div class="row">
67
- <div class="col-6">
68
- <label class="form-label">Debounce delay (ms)</label>
69
- <input id="field-debounce-ms" type="number" class="form-input" min="100" max="1000"
70
- placeholder="300">
71
- <span class="form-hint">Delay after typing stops before search fires (100-1000ms).</span>
72
- </div>
65
+ <div>
66
+ <label class="form-label" for="ssx-debounce">Debounce delay (ms)
67
+ <span data-help="How long after the last keystroke the search runs (100-1000 ms). Shorter feels more instant; longer sends fewer searches while someone is still typing."></span></label>
68
+ <input id="ssx-debounce" type="number" class="form-input" min="100" max="1000" step="50" data-model="debounceMs.value">
73
69
  </div>
74
-
75
70
  </div>
76
71
  </div>
77
72
  </div>
78
73
 
79
- <!-- Trigger & Position Tab -->
80
- <div class="tab-panel" id="tab-trigger-position">
81
- <div class="card mb-4" style="margin-top:1rem;">
82
- <div class="card-body">
74
+ <!-- Trigger & position -->
75
+ <div class="tab-panel">
76
+ <div class="ssx-card">
77
+ <div class="ssx-card-title">How visitors open it
78
+ <span data-help-title="Trigger" data-help="Navbar adds a search icon to the site's navigation bar. Floating button puts a fixed button on every page, in the corner or side you choose - useful when the navigation is busy or hidden on small screens."></span></div>
79
+ <div class="ssx-seg" role="group" aria-label="Display mode">
80
+ <button type="button" data-on-click="useNavbar" data-bind-class="displayMode.value === 'navbar' && 'is-on'">
81
+ <span data-icon="menu" data-icon-size="14"></span> Navbar</button>
82
+ <button type="button" data-on-click="useFloating" data-bind-class="displayMode.value === 'floating' && 'is-on'">
83
+ <span data-icon="search" data-icon-size="14"></span> Floating button</button>
84
+ </div>
85
+ </div>
83
86
 
84
- <div class="row mb-3">
85
- <div class="col">
86
- <label class="form-label">Display Mode</label>
87
- <div id="ss-display-mode" style="display:flex;gap:8px;flex-wrap:wrap;">
88
- <button type="button" class="btn ss-mode-btn" data-mode="navbar">
89
- <span data-icon="menu" data-icon-size="14" style="margin-right:5px;"></span>Navbar
90
- </button>
91
- <button type="button" class="btn ss-mode-btn" data-mode="floating">
92
- <span data-icon="search" data-icon-size="14" style="margin-right:5px;"></span>Floating
93
- Button
94
- </button>
95
- </div>
96
- <input type="hidden" id="field-display-mode" value="navbar">
97
- <span class="form-hint">Navbar adds the icon to the site navigation. Floating button shows a fixed button on every page.</span>
98
- </div>
87
+ <div class="ssx-card" data-bind-hidden="displayMode.value !== 'floating'">
88
+ <div class="ssx-card-title">The floating button</div>
89
+ <div class="ssx-setting">
90
+ <label class="form-label" for="ssx-icononly">Icon only
91
+ <span data-help="On: a round or square icon button. Off: a pill with a Search label beside the icon (size and corners then follow the label)."></span></label>
92
+ <input id="ssx-icononly" type="checkbox" class="form-switch" data-model="iconOnly.value">
93
+ </div>
94
+ <div class="ssx-button-row" data-bind-hidden="!iconOnly.value">
95
+ <div class="ssx-sliders">
96
+ <label class="form-label" for="ssx-size">Size <span class="ssx-readout" data-bind-text="sizeText.value"></span>
97
+ <span data-help="How big the button is (32-96 px). The glyph inside scales with it. 44 px and up is comfortable to tap on a phone."></span></label>
98
+ <input id="ssx-size" type="range" min="32" max="96" step="2" data-model="iconSize.value">
99
+ <label class="form-label" for="ssx-radius">Corners <span class="ssx-readout" data-bind-text="radiusText.value"></span>
100
+ <span data-help="0 is a square; the top of the range is a circle, and it is always half the size - so it moves when the size does."></span></label>
101
+ <input id="ssx-radius" type="range" min="0" step="1" data-model="iconRadius.value">
99
102
  </div>
100
-
101
- <div id="ss-pos-section" style="display:none;">
102
-
103
- <div class="row mb-3">
104
- <div class="col">
105
- <label class="form-check-label">
106
- <input id="field-icon-only" type="checkbox" checked>
107
- Icon only
108
- </label>
109
- <span class="form-hint">When unchecked, shows a "Search" label alongside the icon.</span>
110
- </div>
111
- </div>
112
-
113
- <div class="row mb-3" id="ss-size-row">
114
- <div class="col">
115
- <label class="form-label" for="field-icon-size">Icon size</label>
116
- <div style="display:flex;align-items:center;gap:1rem;flex-wrap:wrap;">
117
- <input id="field-icon-size" type="range" min="32" max="96" step="2" value="52"
118
- style="flex:1 1 200px;max-width:260px;">
119
- <span id="ss-size-value" class="text-sm font-mono" style="min-width:3.5rem;">52px</span>
120
- </div>
121
- <span class="form-hint">How big the floating button is, in pixels (32-96). The search glyph inside
122
- scales with it. 44px and up is the comfortable range for tapping on a phone.</span>
123
- </div>
124
- </div>
125
-
126
- <div class="row mb-3" id="ss-radius-row">
127
- <div class="col">
128
- <label class="form-label" for="field-icon-radius">Icon corner radius</label>
129
- <div style="display:flex;align-items:center;gap:1rem;flex-wrap:wrap;">
130
- <input id="field-icon-radius" type="range" min="0" max="26" step="1" value="26"
131
- style="flex:1 1 200px;max-width:260px;">
132
- <span id="ss-radius-value" class="text-sm font-mono" style="min-width:3.5rem;">26px</span>
133
- <span id="ss-radius-preview" aria-hidden="true"
134
- style="width:52px;height:52px;display:inline-flex;align-items:center;justify-content:center;
135
- background:var(--dm-surface);color:var(--dm-text);
136
- border:1px solid var(--dm-border);border-radius:26px;
137
- box-shadow:0 4px 16px rgba(0,0,0,.14);flex:0 0 auto;transition:border-radius .12s;">
138
- <span data-icon="search" style="width:20px;height:20px;"></span>
139
- </span>
140
- </div>
141
- <span class="form-hint">0 is a square, <span id="ss-radius-max-hint">26</span> a circle - the top of the
142
- range is always half the icon size, so it moves when you change the size above. The preview is the real
143
- button at its real size. Applies to the floating button in icon mode only - the navbar icon has no
144
- surface to round, and the labelled variant is a pill by definition.</span>
145
- </div>
103
+ <span class="ssx-button-preview" aria-hidden="true" title="The button at its real size"
104
+ data-bind-style-width="sizePx.value" data-bind-style-height="sizePx.value" data-bind-style-border-radius="radiusPx.value">
105
+ <span class="ssx-glyph" data-bind-style-width="glyphPx.value" data-bind-style-height="glyphPx.value"><span data-icon="search"></span></span>
106
+ </span>
107
+ </div>
108
+ <div>
109
+ <div class="ssx-card-title ssx-sub">Where it sits
110
+ <span data-help="Click a spot on the little screen. The offsets are the distance from the edges it sits against."></span></div>
111
+ <div class="ssx-where">
112
+ <div class="ssx-screen" role="group" aria-label="Position">
113
+ <button type="button" class="ssx-spot" style="top:6px;left:6px" data-on-click="setPos('top-left')" data-bind-class="position.value === 'top-left' && 'is-on'" title="Top left"></button>
114
+ <button type="button" class="ssx-spot" style="top:6px;right:6px" data-on-click="setPos('top-right')" data-bind-class="position.value === 'top-right' && 'is-on'" title="Top right"></button>
115
+ <button type="button" class="ssx-spot" style="top:calc(50% - 9px);left:6px" data-on-click="setPos('left-center')" data-bind-class="position.value === 'left-center' && 'is-on'" title="Left side"></button>
116
+ <button type="button" class="ssx-spot" style="top:calc(50% - 9px);right:6px" data-on-click="setPos('right-center')" data-bind-class="position.value === 'right-center' && 'is-on'" title="Right side"></button>
117
+ <button type="button" class="ssx-spot" style="bottom:6px;left:6px" data-on-click="setPos('bottom-left')" data-bind-class="position.value === 'bottom-left' && 'is-on'" title="Bottom left"></button>
118
+ <button type="button" class="ssx-spot" style="bottom:6px;right:6px" data-on-click="setPos('bottom-right')" data-bind-class="position.value === 'bottom-right' && 'is-on'" title="Bottom right"></button>
146
119
  </div>
147
-
148
- <div class="row" style="align-items:flex-start;gap:0;">
149
-
150
- <div class="col-auto" style="margin-right:1.5rem;margin-bottom:1rem;">
151
- <label class="form-label" style="margin-bottom:.6rem;">Position</label>
152
- <div id="ss-pos-grid"
153
- style="display:grid;grid-template-columns:1fr 1fr;gap:2px;border:1px solid var(--border-color,rgba(255,255,255,.1));border-radius:10px;overflow:hidden;background:var(--border-color,rgba(255,255,255,.07));width:fit-content;">
154
-
155
- <div class="ss-pos-btn" data-pos="top-left"
156
- style="display:flex;flex-direction:column;align-items:flex-start;padding:.55rem .8rem;cursor:pointer;font-size:.78rem;color:var(--text-muted,#888);border-right:1px solid var(--border-color,rgba(255,255,255,.08));border-bottom:1px solid var(--border-color,rgba(255,255,255,.08));gap:.05rem;user-select:none;transition:background .15s,color .15s;min-width:84px;">
157
- <span style="font-size:.6rem;opacity:.55;letter-spacing:.06em;text-transform:uppercase;line-height:1.2;">Top</span>
158
- <span style="font-weight:600;">↖ Left</span>
159
- </div>
160
- <div class="ss-pos-btn" data-pos="top-right"
161
- style="display:flex;flex-direction:column;align-items:flex-end;padding:.55rem .8rem;cursor:pointer;font-size:.78rem;color:var(--text-muted,#888);border-bottom:1px solid var(--border-color,rgba(255,255,255,.08));gap:.05rem;user-select:none;transition:background .15s,color .15s;min-width:84px;">
162
- <span style="font-size:.6rem;opacity:.55;letter-spacing:.06em;text-transform:uppercase;line-height:1.2;">Top</span>
163
- <span style="font-weight:600;">Right ↗</span>
164
- </div>
165
-
166
- <div class="ss-pos-btn" data-pos="left-center"
167
- style="display:flex;flex-direction:column;align-items:flex-start;padding:.55rem .8rem;cursor:pointer;font-size:.78rem;color:var(--text-muted,#888);border-right:1px solid var(--border-color,rgba(255,255,255,.08));border-bottom:1px solid var(--border-color,rgba(255,255,255,.08));gap:.05rem;user-select:none;transition:background .15s,color .15s;min-width:84px;">
168
- <span style="font-size:.6rem;opacity:.55;letter-spacing:.06em;text-transform:uppercase;line-height:1.2;">Side</span>
169
- <span style="font-weight:600;">← Left</span>
170
- </div>
171
- <div class="ss-pos-btn" data-pos="right-center"
172
- style="display:flex;flex-direction:column;align-items:flex-end;padding:.55rem .8rem;cursor:pointer;font-size:.78rem;color:var(--text-muted,#888);border-bottom:1px solid var(--border-color,rgba(255,255,255,.08));gap:.05rem;user-select:none;transition:background .15s,color .15s;min-width:84px;">
173
- <span style="font-size:.6rem;opacity:.55;letter-spacing:.06em;text-transform:uppercase;line-height:1.2;">Side</span>
174
- <span style="font-weight:600;">Right →</span>
175
- </div>
176
-
177
- <div class="ss-pos-btn" data-pos="bottom-left"
178
- style="display:flex;flex-direction:column;align-items:flex-start;padding:.55rem .8rem;cursor:pointer;font-size:.78rem;color:var(--text-muted,#888);border-right:1px solid var(--border-color,rgba(255,255,255,.08));gap:.05rem;user-select:none;transition:background .15s,color .15s;min-width:84px;">
179
- <span style="font-weight:600;">↙ Left</span>
180
- <span style="font-size:.6rem;opacity:.55;letter-spacing:.06em;text-transform:uppercase;line-height:1.2;">Bottom</span>
181
- </div>
182
- <div class="ss-pos-btn" data-pos="bottom-right"
183
- style="display:flex;flex-direction:column;align-items:flex-end;padding:.55rem .8rem;cursor:pointer;font-size:.78rem;color:var(--text-muted,#888);gap:.05rem;user-select:none;transition:background .15s,color .15s;min-width:84px;">
184
- <span style="font-weight:600;">Right ↘</span>
185
- <span style="font-size:.6rem;opacity:.55;letter-spacing:.06em;text-transform:uppercase;line-height:1.2;">Bottom</span>
186
- </div>
187
-
188
- </div>
189
- <input type="hidden" id="field-position" value="bottom-right">
190
- </div>
191
-
192
- <div class="col-sm-3">
193
- <label class="form-label">Edge offset</label>
194
- <div style="display:flex;flex-direction:column;gap:.55rem;">
195
- <div style="display:flex;align-items:center;gap:.4rem;">
196
- <span style="font-size:.75rem;color:var(--text-muted,#888);width:22px;">H</span>
197
- <input id="field-offset-x" type="number" class="form-input" min="0" max="500"
198
- placeholder="32" style="max-width:80px;">
199
- <span class="text-muted" style="font-size:.8rem;">px</span>
200
- </div>
201
- <div style="display:flex;align-items:center;gap:.4rem;">
202
- <span style="font-size:.75rem;color:var(--text-muted,#888);width:22px;">V</span>
203
- <input id="field-offset-y" type="number" class="form-input" min="0" max="500"
204
- placeholder="32" style="max-width:80px;">
205
- <span class="text-muted" style="font-size:.8rem;">px</span>
206
- </div>
207
- </div>
208
- <p class="form-hint text-muted" style="margin-top:.35rem;font-size:.75rem;">Distance
209
- from the screen edge.</p>
210
- </div>
211
-
120
+ <div class="ssx-offsets">
121
+ <strong data-bind-text="positionText.value"></strong>
122
+ <label class="form-label" for="ssx-offx">From the side (px)</label>
123
+ <input id="ssx-offx" type="number" class="form-input" min="0" max="500" data-model="offsetX.value">
124
+ <label class="form-label" for="ssx-offy">From the top or bottom (px)</label>
125
+ <input id="ssx-offy" type="number" class="form-input" min="0" max="500" data-model="offsetY.value">
212
126
  </div>
213
127
  </div>
214
-
215
128
  </div>
216
129
  </div>
217
130
  </div>
218
131
 
219
- <!-- How it works Tab -->
220
- <div class="tab-panel" id="tab-how-it-works">
221
- <div class="card mb-4" style="margin-top:1rem;">
222
- <div class="card-body">
223
- <p class="text-muted mb-2">Search adds a search icon to the public navbar, or a floating button
224
- if you prefer. Visitors can click it or use the keyboard shortcut to open a full-screen overlay
225
- with live results.</p>
226
- <ul class="text-muted" style="padding-left:1.25rem;line-height:1.8;">
227
- <li>Results are weighted: title matches score highest, followed by tags, description, and page
228
- content.
229
- </li>
230
- <li>Draft pages and private pages are excluded from results.</li>
231
- <li>An authored context menu can offer "Site search" as an action, which opens the same overlay.</li>
232
- <li>Use <kbd>↑</kbd> <kbd>↓</kbd> to navigate results, <kbd>↵</kbd> to open, <kbd>Esc</kbd> to
233
- close.
234
- </li>
235
- </ul>
236
- </div>
132
+ <!-- Try it -->
133
+ <div class="tab-panel">
134
+ <div class="ssx-card">
135
+ <div class="ssx-card-title">Try a search
136
+ <span data-help-title="Try it" data-help="Runs the site's own search as a visitor would - published, public pages only, ranked the same way - using the Max results you are editing, before you Save. Right-click a result (or ⋮) to open or edit the page."></span></div>
137
+ <input type="search" class="form-input ssx-q" placeholder="Search your pages…" aria-label="Try a search" data-model="query.value">
138
+ <p class="ssx-note" data-bind-hidden="!previewNote.value" data-bind-text="previewNote.value"></p>
139
+ <p class="ssx-note is-bad" data-bind-hidden="!previewError.value" data-bind-text="previewError.value"></p>
140
+ <p class="ssx-note" data-bind-hidden="!previewMeta.value" data-bind-text="previewMeta.value"></p>
141
+ <div class="ssx-list" data-each="results.value key=id"></div>
237
142
  </div>
238
143
  </div>
239
144
 
145
+ <!-- How it works -->
146
+ <div class="tab-panel">
147
+ <div class="ssx-card">
148
+ <div class="ssx-card-title">How search works</div>
149
+ <ul class="ssx-how">
150
+ <li>Visitors click the trigger (or press the shortcut) and get a full-screen overlay with live results.</li>
151
+ <li>Results are ranked: a title match scores highest, then tags, the description, and the page's text.</li>
152
+ <li>Draft and private pages never appear in results.</li>
153
+ <li>An authored context menu can offer "Site search" as an action; it opens the same overlay.</li>
154
+ <li>In the overlay: <kbd>↑</kbd> <kbd>↓</kbd> move, <kbd>↵</kbd> opens, <kbd>Esc</kbd> closes.</li>
155
+ </ul>
156
+ </div>
157
+ </div>
240
158
  </div>
241
159
  </div>
160
+
161
+ <style>
162
+ .ssx { display: flex; flex-direction: column; gap: .75rem; }
163
+ .ssx [hidden] { display: none !important; }
164
+ .ssx-alert { margin: 0; display: flex; flex-wrap: wrap; gap: .5rem; align-items: center; padding: .6rem .8rem; border-radius: var(--dm-radius-md);
165
+ background: color-mix(in srgb, var(--dm-warning) 16%, transparent); color: var(--dm-text); }
166
+ .ssx-alert.is-dirty { background: color-mix(in srgb, var(--dm-primary) 12%, transparent); }
167
+ .ssx-spacer { flex: 1; }
168
+ .ssx-card { border: 1px solid var(--dm-border); border-radius: var(--dm-radius-md); padding: .75rem .9rem; display: flex; flex-direction: column; gap: .7rem; margin-top: .75rem; }
169
+ .ssx-card-title { display: flex; align-items: center; gap: .35rem; font-weight: 600; }
170
+ .ssx-sub { font-size: var(--dm-text-sm); }
171
+ .ssx-grid { display: grid; grid-template-columns: 1fr 1fr; gap: .75rem 1rem; }
172
+ .ssx-grid-3 { grid-template-columns: 1fr 1fr 1fr; }
173
+ .ssx-wide { grid-column: 1 / -1; }
174
+ .ssx .form-label { display: flex; align-items: center; gap: .3rem; }
175
+ .ssx-setting { display: flex; align-items: center; justify-content: space-between; gap: .75rem; }
176
+ .ssx-setting .form-label { margin: 0; }
177
+ .ssx-note { margin: 0; color: var(--dm-text-muted); font-size: var(--dm-text-sm); }
178
+ .ssx-note.is-bad { color: var(--dm-danger); }
179
+ .ssx-chips { display: flex; flex-wrap: wrap; gap: .4rem; }
180
+ .ssx-chip { display: inline-flex; align-items: center; font-size: var(--dm-text-xs); font-weight: 600; padding: .15rem .55rem; border-radius: 999px; white-space: nowrap;
181
+ background: color-mix(in srgb, var(--dm-text) 10%, transparent); color: var(--dm-text-muted); }
182
+ .ssx-chip.is-ok { background: color-mix(in srgb, var(--dm-success) 16%, transparent); color: var(--dm-success); }
183
+ .ssx-chip.is-warn { background: color-mix(in srgb, var(--dm-warning) 18%, transparent); color: var(--dm-warning); }
184
+ .ssx-seg { display: inline-flex; border: 1px solid var(--dm-border); border-radius: var(--dm-radius-md); overflow: hidden; align-self: flex-start; }
185
+ .ssx-seg button { display: inline-flex; align-items: center; gap: .35rem; border: 0; background: none; color: var(--dm-text); padding: .35rem .8rem; cursor: pointer; font: inherit; font-size: var(--dm-text-sm); }
186
+ .ssx-seg button + button { border-left: 1px solid var(--dm-border); }
187
+ .ssx-seg button:hover { background: var(--dm-table-hover-bg, color-mix(in srgb, var(--dm-text) 6%, transparent)); }
188
+ .ssx-seg button.is-on { background: var(--dm-primary); color: var(--dm-primary-text); }
189
+ .ssx-button-row { display: flex; align-items: center; gap: 1.5rem; flex-wrap: wrap; }
190
+ .ssx-sliders { display: flex; flex-direction: column; gap: .35rem; flex: 1 1 240px; max-width: 320px; }
191
+ .ssx-readout { font-family: var(--dm-font-mono, monospace); color: var(--dm-text-muted); font-weight: 400; }
192
+ .ssx-button-preview { display: inline-flex; align-items: center; justify-content: center; flex: 0 0 auto;
193
+ background: var(--dm-surface); color: var(--dm-text); border: 1px solid var(--dm-border);
194
+ box-shadow: 0 4px 16px color-mix(in srgb, var(--dm-text) 14%, transparent); transition: border-radius .12s; }
195
+ .ssx-glyph { display: inline-flex; }
196
+ .ssx-glyph svg { width: 100%; height: 100%; }
197
+ .ssx-where { display: flex; gap: 1.25rem; align-items: flex-start; flex-wrap: wrap; }
198
+ .ssx-screen { position: relative; width: 180px; height: 120px; border: 1px solid var(--dm-border); border-radius: var(--dm-radius-md);
199
+ background: color-mix(in srgb, var(--dm-text) 4%, transparent); flex: 0 0 auto; }
200
+ .ssx-spot { position: absolute; width: 18px; height: 18px; border-radius: 50%; border: 1px dashed var(--dm-border); background: var(--dm-surface); cursor: pointer; padding: 0; }
201
+ .ssx-spot:hover { border-color: var(--dm-primary); }
202
+ .ssx-spot.is-on { background: var(--dm-primary); border: 1px solid var(--dm-primary); }
203
+ .ssx-offsets { display: flex; flex-direction: column; gap: .3rem; min-width: 12rem; }
204
+ .ssx-offsets .form-input { max-width: 8rem; }
205
+ .ssx-q { max-width: 32rem; }
206
+ .ssx-list { display: flex; flex-direction: column; }
207
+ .ssx-row { display: flex; align-items: flex-start; gap: .75rem; padding: .55rem .35rem; border-bottom: 1px solid var(--dm-border); cursor: pointer; }
208
+ .ssx-row:last-child { border-bottom: 0; }
209
+ .ssx-row:hover, .ssx-row:focus-visible { background: var(--dm-table-hover-bg, color-mix(in srgb, var(--dm-text) 6%, transparent)); outline: none; }
210
+ .ssx-rank { min-width: 1.5rem; color: var(--dm-text-muted); font-variant-numeric: tabular-nums; padding-top: .1rem; }
211
+ .ssx-main { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: .1rem; }
212
+ .ssx-main small { color: var(--dm-text-muted); font-size: var(--dm-text-xs); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
213
+ .ssx-snippet { color: var(--dm-text); font-size: var(--dm-text-sm); opacity: .85; }
214
+ .ssx-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; }
215
+ .ssx-kebab:hover { color: var(--dm-primary); }
216
+ .ssx-how { margin: 0; padding-left: 1.25rem; line-height: 1.8; color: var(--dm-text-muted); }
217
+ @media (max-width: 720px) { .ssx-grid, .ssx-grid-3 { grid-template-columns: 1fr; } }
218
+ </style>