Refine settings accordion and API examples modal
- Replace the two-step settings nav with a sticky accordion that tracks active sections - Restyle the settings rail with opacity-based active state and hash-aware opening - Reinitialize and clean up API example Ace editors across modal reopen cycles - Preserve modal html classes and center settings loading/error states across the full modal body
Alessandro committed
May 7, 2026 at 19:41 UTC
36c2e3d6b8f9cce7230585c3a14bee082eea12d2
5 files changed
+357
-93
webui/components/settings/external/api-examples.html
+76
-9
@@ -5,7 +5,11 @@
5
</head>
6
7
<body>
8
- <div x-data>
8
+ <div
9
+ x-data
10
+ x-init="window.initExternalApiExamples && window.initExternalApiExamples($el)"
11
+ x-destroy="window.destroyExternalApiExamples && window.destroyExternalApiExamples($el)"
12
+ >
13
<p>Agent Zero provides external API endpoints for integration with other applications.</p>
14
<p>These endpoints use API key authentication and support text messages and file attachments.</p>
15
@@ -193,7 +197,51 @@
197
<script type="module">
198
import * as API from "/js/api.js";
199
196
- setTimeout(async () => {
200
+ const mountedEditors = new WeakMap();
201
+
202
+ function scheduleEditorResize(root, editors) {
203
+ const state = {
204
+ editors,
205
+ frameId: null,
206
+ timeoutIds: [],
207
+ };
208
+ const resize = () => {
209
+ if (mountedEditors.get(root) !== state) return;
210
+ editors.forEach((editor) => editor.resize(true));
211
+ };
212
+ state.frameId = requestAnimationFrame(() => {
213
+ state.frameId = null;
214
+ resize();
215
+ state.timeoutIds = [
216
+ setTimeout(resize, 80),
217
+ setTimeout(resize, 240),
218
+ ];
219
+ });
220
+ mountedEditors.set(root, state);
221
+ }
222
+
223
+ function destroyEditors(root) {
224
+ const state = mountedEditors.get(root);
225
+ if (!state) return;
226
+
227
+ if (state.frameId) {
228
+ cancelAnimationFrame(state.frameId);
229
+ }
230
+ state.timeoutIds.forEach((timeoutId) => clearTimeout(timeoutId));
231
+
232
+ state.editors.forEach((editor) => {
233
+ try {
234
+ editor.destroy();
235
+ editor.container.removeAttribute("data-ace-editor-id");
236
+ editor.container.textContent = "";
237
+ } catch (error) {
238
+ console.warn("Failed to destroy API example editor:", error);
239
+ }
240
+ });
241
+ mountedEditors.delete(root);
242
+ }
243
+
244
+ async function buildExamples() {
245
const url = window.location.origin;
246
247
// Fetch token from settings API
@@ -670,8 +718,7 @@ async function sendMessageWithProject() {
718
// Call the function
719
sendMessageWithProject();`;
720
673
- // Initialize ACE editors
674
- const editors = [
721
+ return [
722
{ id: "api-basic-example", content: basicExample },
723
{ id: "api-continuation-example", content: continuationExample },
724
{ id: "api-attachment-example", content: attachmentExample },
@@ -682,9 +729,25 @@ sendMessageWithProject();`;
729
{ id: "api-reset-example", content: resetExample },
730
{ id: "api-files-get-example", content: filesGetExample }
731
];
732
+ }
733
+
734
+ window.destroyExternalApiExamples = destroyEditors;
735
+
736
+ window.initExternalApiExamples = async (root) => {
737
+ if (!root || !window.ace) return;
738
686
- editors.forEach(({ id, content }) => {
687
- const editor = ace.edit(id);
739
+ destroyEditors(root);
740
+ const examples = await buildExamples();
741
+ if (!root.isConnected) return;
742
+
743
+ const editors = [];
744
+
745
+ examples.forEach(({ id, content }) => {
746
+ const container = root.querySelector(`#${id}`);
747
+ if (!container) return;
748
+ container.textContent = "";
749
+
750
+ const editor = ace.edit(container);
751
const dark = localStorage.getItem("darkMode");
752
if (dark != "false") {
753
editor.setTheme("ace/theme/github_dark");
@@ -692,11 +755,15 @@ sendMessageWithProject();`;
755
editor.setTheme("ace/theme/tomorrow");
756
}
757
editor.session.setMode("ace/mode/javascript");
695
- editor.setValue(content);
696
- editor.clearSelection();
758
+ editor.setValue(content, -1);
759
editor.setReadOnly(true);
760
+ editor.renderer.setShowGutter(true);
761
+ editor.renderer.setPadding(8);
762
+ editors.push(editor);
763
});
699
- }, 0);
764
+
765
+ scheduleEditorResize(root, editors);
766
+ };
767
</script>
768
</div>
769
webui/components/settings/settings-store.js
+138
-12
@@ -6,6 +6,8 @@ import { store as notificationStore } from "/components/notifications/notificati
6
const VIEW_MODE_STORAGE_KEY = "settingsActiveTab";
7
const DEFAULT_TAB = "agent";
8
const UPDATE_STATUS_REFRESH_COOLDOWN_MS = 60 * 1000;
9
+// Match the modal header/padding breathing room before promoting a section link.
10
+const SECTION_ACTIVATION_OFFSET = 56;
11
12
const TAB_ITEMS = Object.freeze([
13
{
@@ -93,7 +95,10 @@ const model = {
95
settings: null,
96
additional: null,
97
workdirFileStructureTestOutput: "",
96
- navMode: "categories",
98
+ _activeSection: null,
99
+ _paneScrollHandler: null,
100
+ _paneScrollPane: null,
101
+ _scrollSyncFrame: null,
102
_updateStatusRefreshedAt: 0,
103
104
// Tab state
@@ -103,8 +108,12 @@ const model = {
108
},
109
set activeTab(value) {
110
const previous = this._activeTab;
106
- this._activeTab = value;
107
- this.applyActiveTab(previous, value);
111
+ this._activeTab = this.normalizeTabId(value);
112
+ this.applyActiveTab(previous, this._activeTab);
113
+ },
114
+
115
+ get activeSection() {
116
+ return this._activeSection || this.getFirstSectionId(this.activeTab);
117
},
118
119
// Lifecycle
@@ -112,8 +121,9 @@ const model = {
121
// Restore persisted tab
122
try {
123
const saved = localStorage.getItem(VIEW_MODE_STORAGE_KEY);
115
- if (saved) this._activeTab = saved;
124
+ if (saved) this._activeTab = this.normalizeTabId(saved);
125
} catch {}
126
+ this._activeSection = this.getFirstSectionId(this._activeTab);
127
},
128
129
async onOpen() {
@@ -138,30 +148,51 @@ const model = {
148
149
this.refreshUpdateStatus();
150
151
+ const hashSectionId = this.getHashSectionId();
152
+ const openedHashSection = hashSectionId
153
+ ? this.activateSection(hashSectionId, { persist: false })
154
+ : false;
155
+
156
// Trigger tab activation for current tab
157
+ this._activeTab = this.normalizeTabId(this._activeTab);
158
this.applyActiveTab(null, this._activeTab);
159
+ this.bindPaneScroll();
160
+
161
+ if (openedHashSection) {
162
+ this.scrollToSection(hashSectionId);
163
+ }
164
},
165
166
cleanup() {
167
+ this.unbindPaneScroll();
168
this.settings = null;
169
this.additional = null;
170
this.error = null;
171
this.isLoading = false;
150
- this.navMode = "categories";
172
},
173
174
// Tab management
175
applyActiveTab(previous, current) {
176
+ if (!this.sectionBelongsToTab(this._activeSection, current)) {
177
+ this._activeSection = this.getFirstSectionId(current);
178
+ }
179
+
180
// Persist
181
try {
182
localStorage.setItem(VIEW_MODE_STORAGE_KEY, current);
183
} catch {}
184
+
185
+ this.bindPaneScroll();
186
},
187
188
switchTab(tabName) {
189
this.activeTab = tabName;
190
},
191
192
+ normalizeTabId(tabName) {
193
+ return TAB_ITEMS.some((item) => item.id === tabName) ? tabName : DEFAULT_TAB;
194
+ },
195
+
196
get navItems() {
197
return TAB_ITEMS;
198
},
@@ -174,21 +205,62 @@ const model = {
205
return this.activeTabItem?.sections || [];
206
},
207
208
+ getFirstSectionId(tabName = this.activeTab) {
209
+ const tab = TAB_ITEMS.find((item) => item.id === tabName) || TAB_ITEMS[0];
210
+ return tab?.sections?.[0]?.id || null;
211
+ },
212
+
213
+ getTabIdForSection(sectionId) {
214
+ if (!sectionId) return null;
215
+ const tab = TAB_ITEMS.find((item) =>
216
+ item.sections?.some((section) => section.id === sectionId)
217
+ );
218
+ return tab?.id || null;
219
+ },
220
+
221
+ sectionBelongsToTab(sectionId, tabName = this.activeTab) {
222
+ if (!sectionId) return false;
223
+ return this.getTabIdForSection(sectionId) === tabName;
224
+ },
225
+
226
+ getHashSectionId() {
227
+ const rawHash = window.location.hash || "";
228
+ if (!rawHash.startsWith("#section-")) return null;
229
+ try {
230
+ return decodeURIComponent(rawHash.slice(1));
231
+ } catch {
232
+ return rawHash.slice(1);
233
+ }
234
+ },
235
+
236
+ activateSection(sectionId, { persist = true } = {}) {
237
+ const tabId = this.getTabIdForSection(sectionId);
238
+ if (!tabId) return false;
239
+
240
+ const previous = this._activeTab;
241
+ this._activeTab = tabId;
242
+ this._activeSection = sectionId;
243
+ if (persist) {
244
+ this.applyActiveTab(previous, tabId);
245
+ }
246
+ if (tabId === "backup") this.refreshUpdateStatus();
247
+ return true;
248
+ },
249
+
250
enterTab(tabName) {
251
this.activeTab = tabName;
179
- this.navMode = "sections";
252
+ this._activeSection = this.getFirstSectionId(this.activeTab);
253
this.resetPaneScroll();
254
if (tabName === "backup") this.refreshUpdateStatus();
255
},
256
184
- backToCategories() {
185
- this.navMode = "categories";
186
- },
187
-
257
resetPaneScroll() {
258
requestAnimationFrame(() => {
259
const pane = this.getSettingsPane();
191
- if (pane) pane.scrollTop = 0;
260
+ if (pane) {
261
+ pane.scrollTop = 0;
262
+ this.updateActiveSectionFromScroll();
263
+ }
264
});
265
},
266
@@ -196,6 +268,57 @@ const model = {
268
return document.querySelector(".modal-inner.settings-modal .settings-pane");
269
},
270
271
+ bindPaneScroll() {
272
+ requestAnimationFrame(() => {
273
+ const pane = this.getSettingsPane();
274
+ if (!pane || this._paneScrollPane === pane) {
275
+ if (pane) this.updateActiveSectionFromScroll();
276
+ return;
277
+ }
278
+
279
+ this.unbindPaneScroll();
280
+ this._paneScrollPane = pane;
281
+ this._paneScrollHandler = () => this.updateActiveSectionFromScroll();
282
+ pane.addEventListener("scroll", this._paneScrollHandler, { passive: true });
283
+ this.updateActiveSectionFromScroll();
284
+ });
285
+ },
286
+
287
+ unbindPaneScroll() {
288
+ if (this._paneScrollPane && this._paneScrollHandler) {
289
+ this._paneScrollPane.removeEventListener("scroll", this._paneScrollHandler);
290
+ }
291
+ if (this._scrollSyncFrame) {
292
+ cancelAnimationFrame(this._scrollSyncFrame);
293
+ }
294
+ this._paneScrollPane = null;
295
+ this._paneScrollHandler = null;
296
+ this._scrollSyncFrame = null;
297
+ },
298
+
299
+ updateActiveSectionFromScroll() {
300
+ if (this._scrollSyncFrame) return;
301
+ this._scrollSyncFrame = requestAnimationFrame(() => {
302
+ this._scrollSyncFrame = null;
303
+ const pane = this.getSettingsPane();
304
+ if (!pane) return;
305
+
306
+ const paneRect = pane.getBoundingClientRect();
307
+ const activationTop = paneRect.top + SECTION_ACTIVATION_OFFSET;
308
+ let activeId = this.getFirstSectionId(this.activeTab);
309
+
310
+ for (const section of this.sectionItems) {
311
+ const target = this.getSectionTarget(section.id, pane);
312
+ if (!target || target.offsetParent === null) continue;
313
+ if (target.getBoundingClientRect().top <= activationTop) {
314
+ activeId = section.id;
315
+ }
316
+ }
317
+
318
+ this._activeSection = activeId;
319
+ });
320
+ },
321
+
322
get selfUpdate() {
323
return globalThis.Alpine?.store?.("selfUpdateStore") || null;
324
},
@@ -210,7 +333,9 @@ const model = {
333
334
scrollToSection(sectionId, event = null) {
335
event?.preventDefault?.();
213
- this.navMode = "sections";
336
+ if (!this.activateSection(sectionId)) {
337
+ this._activeSection = sectionId;
338
+ }
339
340
const performScroll = () => {
341
const pane = this.getSettingsPane();
@@ -231,6 +356,7 @@ const model = {
356
behavior: "smooth",
357
});
358
history.replaceState(null, "", `#${sectionId}`);
359
+ this.updateActiveSectionFromScroll();
360
};
361
362
requestAnimationFrame(() => requestAnimationFrame(performScroll));
webui/components/settings/settings.html
+48
-37
@@ -29,45 +29,52 @@
29
<div x-show="$store.settings.settings" class="settings-content">
30
<!-- Tab Navigation -->
31
<aside class="settings-tabs-container" aria-label="Settings categories">
32
- <div class="settings-tabs no-scrollbar" role="tablist" aria-orientation="vertical" x-show="$store.settings.navMode === 'categories'">
32
+ <div class="settings-tabs no-scrollbar" role="tablist" aria-orientation="vertical">
33
<template x-for="item in $store.settings.navItems" :key="item.id">
34
- <button type="button"
35
- class="settings-tab"
36
- role="tab"
37
- :aria-selected="$store.settings.activeTab === item.id"
38
- :class="{
39
- 'active': $store.settings.activeTab === item.id,
40
- 'settings-tab-attention': $store.settings.navItemHasAttention(item)
41
- }"
42
- @click="$store.settings.enterTab(item.id)">
43
- <span class="material-symbols-outlined" aria-hidden="true" x-text="item.icon"></span>
44
- <span class="settings-tab-label" x-text="item.label"></span>
45
- <span class="settings-attention-dot"
46
- x-show="$store.settings.navItemHasAttention(item)"
47
- aria-hidden="true"></span>
48
- </button>
49
- </template>
50
- </div>
34
+ <div class="settings-nav-group"
35
+ :class="{'settings-nav-group-active': $store.settings.activeTab === item.id}">
36
+ <button type="button"
37
+ class="settings-tab settings-parent-tab"
38
+ role="tab"
39
+ :aria-selected="$store.settings.activeTab === item.id"
40
+ :aria-expanded="$store.settings.activeTab === item.id"
41
+ :class="{
42
+ 'active': $store.settings.activeTab === item.id,
43
+ 'settings-tab-attention': $store.settings.navItemHasAttention(item)
44
+ }"
45
+ @click="$store.settings.enterTab(item.id)">
46
+ <span class="material-symbols-outlined" aria-hidden="true" x-text="item.icon"></span>
47
+ <span class="settings-tab-label" x-text="item.label"></span>
48
+ <span class="settings-tab-meta">
49
+ <span class="settings-attention-dot"
50
+ x-show="$store.settings.navItemHasAttention(item)"
51
+ aria-hidden="true"></span>
52
+ <span class="material-symbols-outlined settings-tab-chevron"
53
+ aria-hidden="true">expand_more</span>
54
+ </span>
55
+ </button>
56
52
- <div class="settings-tabs no-scrollbar settings-section-tabs" x-show="$store.settings.navMode === 'sections'" style="display: none;">
53
- <button type="button"
54
- class="settings-tab settings-back-tab"
55
- @click="$store.settings.backToCategories()">
56
- <span class="material-symbols-outlined" aria-hidden="true">arrow_back</span>
57
- <span class="settings-tab-label">< Back</span>
58
- </button>
59
- <div class="settings-tabs-heading" x-text="$store.settings.activeTabItem?.label || 'Settings'"></div>
60
- <template x-for="item in $store.settings.sectionItems" :key="item.id">
61
- <a class="settings-tab settings-section-link"
62
- :class="{'settings-tab-attention': $store.settings.sectionItemHasAttention(item)}"
63
- :href="`#${item.id}`"
64
- @click="$store.settings.scrollToSection(item.id, $event)">
65
- <span class="material-symbols-outlined" aria-hidden="true" x-text="item.icon"></span>
66
- <span class="settings-tab-label" x-text="item.label"></span>
67
- <span class="settings-attention-dot"
68
- x-show="$store.settings.sectionItemHasAttention(item)"
69
- aria-hidden="true"></span>
70
- </a>
57
+ <div class="settings-section-list"
58
+ x-show="$store.settings.activeTab === item.id"
59
+ x-transition.opacity.duration.120ms>
60
+ <template x-for="section in item.sections" :key="section.id">
61
+ <a class="settings-section-link"
62
+ :class="{
63
+ 'active': $store.settings.activeSection === section.id,
64
+ 'settings-tab-attention': $store.settings.sectionItemHasAttention(section)
65
+ }"
66
+ :aria-current="$store.settings.activeSection === section.id ? 'true' : null"
67
+ :href="`#${section.id}`"
68
+ @click="$store.settings.scrollToSection(section.id, $event)">
69
+ <span class="material-symbols-outlined" aria-hidden="true" x-text="section.icon"></span>
70
+ <span class="settings-tab-label" x-text="section.label"></span>
71
+ <span class="settings-attention-dot"
72
+ x-show="$store.settings.sectionItemHasAttention(section)"
73
+ aria-hidden="true"></span>
74
+ </a>
75
+ </template>
76
+ </div>
77
+ </div>
78
</template>
79
</div>
80
</aside>
@@ -118,9 +125,13 @@
125
.settings-loading,
126
.settings-error {
127
display: flex;
128
+ flex: 1 1 auto;
129
align-items: center;
130
justify-content: center;
131
+ align-self: stretch;
132
gap: 0.75rem;
133
+ width: 100%;
134
+ min-height: 0;
135
padding: 2rem;
136
color: var(--color-text-secondary, #999);
137
font-size: 1rem;
webui/css/settings.css
+92
-33
@@ -150,8 +150,17 @@ select:disabled {
150
151
/* Settings modal shell */
152
.modal-inner.settings-modal {
153
+ right: auto;
154
+ bottom: auto;
155
+ z-index: 2;
156
+ display: flex;
157
+ align-items: stretch;
158
+ justify-content: flex-start;
159
width: min(92vw, 1180px);
160
height: min(88vh, 900px);
161
+ padding: 0;
162
+ overflow: hidden;
163
+ background: color-mix(in srgb, var(--color-background) 94%, #000 6%);
164
}
165
166
.modal-inner.settings-modal .modal-scroll,
@@ -209,6 +218,13 @@ select:disabled {
218
overflow-y: auto;
219
}
220
221
+.settings-nav-group {
222
+ display: flex;
223
+ flex-direction: column;
224
+ gap: 3px;
225
+ min-width: 0;
226
+}
227
+
228
.settings-tab {
229
display: grid;
230
grid-template-columns: 22px minmax(0, 1fr) auto;
@@ -217,8 +233,8 @@ select:disabled {
233
width: 100%;
234
min-height: 38px;
235
padding: 0 10px;
220
- border: 1px solid transparent;
221
- border-radius: 7px;
236
+ border: 0;
237
+ border-radius: 0;
238
appearance: none;
239
background: transparent;
240
color: var(--color-text);
@@ -227,9 +243,27 @@ select:disabled {
243
font-size: 0.83rem;
244
font-weight: 560;
245
line-height: 1.1;
246
+ opacity: 0.68;
247
text-align: left;
248
text-decoration: none;
232
- transition: background-color 0.16s ease, border-color 0.16s ease, opacity 0.16s ease;
249
+ transition: color 0.16s ease, opacity 0.16s ease;
250
+}
251
+
252
+.settings-tab-meta {
253
+ display: inline-flex;
254
+ align-items: center;
255
+ justify-content: flex-end;
256
+ gap: 6px;
257
+ min-width: 18px;
258
+}
259
+
260
+.settings-tab-chevron {
261
+ transform: rotate(-90deg);
262
+ transition: transform 0.16s ease;
263
+}
264
+
265
+.settings-nav-group-active .settings-tab-chevron {
266
+ transform: rotate(0deg);
267
}
268
269
.settings-tab-label {
@@ -239,34 +273,32 @@ select:disabled {
273
white-space: nowrap;
274
}
275
242
-.settings-tab .material-symbols-outlined {
276
+.settings-tab .material-symbols-outlined,
277
+.settings-section-link .material-symbols-outlined {
278
color: var(--color-text-muted);
279
font-size: 18px;
280
}
281
282
.settings-tab:hover,
248
-.settings-tab.active {
249
- border-color: color-mix(in srgb, var(--color-primary) 25%, var(--color-border));
250
- background: color-mix(in srgb, var(--color-background-hover) 56%, transparent);
283
+.settings-tab.active,
284
+.settings-section-link:hover,
285
+.settings-section-link.active {
286
opacity: 1;
287
}
288
254
-.settings-tab.active {
289
+.settings-tab.active,
290
+.settings-section-link.active {
291
color: var(--color-text);
292
font-weight: 750;
293
}
294
259
-.settings-tab.active .material-symbols-outlined {
295
+.settings-tab.active .material-symbols-outlined,
296
+.settings-section-link.active .material-symbols-outlined {
297
color: var(--color-primary);
298
}
299
300
.settings-tab-attention {
264
- border-color: color-mix(in srgb, #f2bf4b 42%, var(--color-border));
265
- background: linear-gradient(
266
- 90deg,
267
- color-mix(in srgb, #f2bf4b 13%, transparent),
268
- transparent 62%
269
- );
301
+ opacity: 1;
302
}
303
304
.settings-tab-attention .material-symbols-outlined {
@@ -276,27 +308,49 @@ select:disabled {
308
.settings-attention-dot {
309
width: 7px;
310
height: 7px;
311
+ flex: 0 0 auto;
312
border-radius: 999px;
313
background: #f2bf4b;
314
box-shadow: 0 0 0 3px color-mix(in srgb, #f2bf4b 18%, transparent);
315
}
316
284
-.settings-back-tab {
285
- margin-bottom: 4px;
286
- color: var(--color-text-muted);
287
-}
288
-
289
-.settings-tabs-heading {
290
- margin: 8px 8px 6px;
291
- color: var(--color-primary);
292
- font-size: 0.72rem;
293
- font-weight: 750;
294
- letter-spacing: 0;
295
- text-transform: uppercase;
317
+.settings-section-list {
318
+ display: flex;
319
+ flex-direction: column;
320
+ gap: 2px;
321
+ min-width: 0;
322
+ margin: 0 0 6px 30px;
323
+ padding-left: 10px;
324
+ border-left: 1px solid color-mix(in srgb, var(--color-border) 54%, transparent);
325
}
326
327
.settings-section-link {
328
+ display: grid;
329
+ grid-template-columns: 20px minmax(0, 1fr) auto;
330
+ align-items: center;
331
+ gap: 7px;
332
+ min-height: 32px;
333
+ padding: 0 9px;
334
+ border: 0;
335
+ border-radius: 0;
336
+ background: transparent;
337
color: var(--color-text);
338
+ font-size: 0.78rem;
339
+ font-weight: 560;
340
+ line-height: 1.12;
341
+ opacity: 0.72;
342
+ text-align: left;
343
+ text-decoration: none;
344
+ transition: color 0.16s ease, opacity 0.16s ease;
345
+}
346
+
347
+.settings-section-link .material-symbols-outlined {
348
+ font-size: 17px;
349
+}
350
+
351
+.settings-section-link:hover,
352
+.settings-section-link.active {
353
+ opacity: 1;
354
}
355
356
.settings-pane {
@@ -599,25 +653,30 @@ select:disabled {
653
top: 0;
654
z-index: 3;
655
height: auto;
656
+ max-height: min(42vh, 300px);
657
padding: 7px 8px;
603
- overflow-x: auto;
658
+ overflow: hidden;
659
border-right: 0;
660
border-bottom: 1px solid color-mix(in srgb, var(--color-border) 58%, transparent);
661
}
662
663
.settings-tabs {
609
- flex-direction: row;
610
- width: max-content;
664
+ flex-direction: column;
665
+ width: 100%;
666
min-width: 100%;
612
- overflow-y: hidden;
667
+ overflow-y: auto;
668
}
669
670
.settings-tab {
616
- width: auto;
617
- min-width: max-content;
671
+ width: 100%;
672
+ min-width: 0;
673
padding: 0 9px;
674
}
675
676
+ .settings-section-list {
677
+ margin-left: 28px;
678
+ }
679
+
680
.settings-pane {
681
padding: 14px 16px 24px;
682
}
webui/js/modals.js
+3
-2
@@ -250,9 +250,10 @@ export async function openModal(modalPath, beforeClose = null) {
250
.then(async (doc) => {
251
// Set the title from the document
252
modal.title.innerHTML = doc.title || modalPath;
253
- if (doc.html && doc.html.classList) {
253
+ const htmlElement = doc.documentElement;
254
+ if (htmlElement && htmlElement.classList) {
255
const inner = modal.element.querySelector(".modal-inner");
255
- if (inner) inner.classList.add(...doc.html.classList);
256
+ if (inner) inner.classList.add(...htmlElement.classList);
257
}
258
if (doc.body && doc.body.classList) {
259
modal.body.classList.add(...doc.body.classList);