refactor settings and scheduler

- Simplified task detail opening logic by integrating it into the `settingsModalStore` - Updated the visibility condition for the task detail view in `scheduler-task-detail.html` to rely solely on the selected task state rm attributes from components simplify task display logic settings components init scheduler componentize - Removed the inline scheduler settings script from `index.html` and replaced it with a new component structure in `scheduler-settings.html`, `scheduler-task-editor.html`, `scheduler-task-list.html`, and `scheduler-task-detail.html`. - Introduced a dedicated `scheduler-store.js` to manage state and logic for the scheduler, enhancing maintainability and separation of concerns. - Updated the `index.js` to remove the now obsolete `openTaskDetail` function, integrating task detail handling within the new store. - Removed the deprecated `scheduler.js` file, consolidating functionality into the new component architecture. settings modal store rename - Replaced all instances of `$store.settingsModalStore` with `$store.settingsStore` across various settings components. scheduler tab content x-if

Alessandro committed Dec 11, 2025 at 11:17 UTC 400095cbe92815424844309855605d57a785aed9
17 files changed +163 -108
webui/components/modals/file-browser/file-browser-store.js
+2 -3
@@ -172,7 +172,6 @@ const model = {
172
173 // --- File actions --------------------------------------------------------
174 async deleteFile(file) {
175 - if (!confirm(`Are you sure you want to delete ${file.name}?`)) return;
175 try {
176 const resp = await fetchApi("/delete_work_dir_file", {
177 method: "POST",
@@ -186,9 +185,9 @@ const model = {
185 this.browser.entries = this.browser.entries.filter(
186 (e) => e.path !== file.path
187 );
189 - alert("File deleted successfully.");
188 + window.toastFrontendSuccess("File deleted successfully", "File Deleted");
189 } else {
191 - alert(`Error deleting file: ${await resp.text()}`);
190 + window.toastFrontendError(`Error deleting file: ${await resp.text()}`, "Delete Error");
191 }
192 } catch (e) {
193 window.toastFrontendError(
webui/components/modals/file-browser/file-browser.html
+4 -39
@@ -48,11 +48,11 @@
48 <div class="file-size" x-text="$store.fileBrowser.formatFileSize(file.size)"></div>
49 <div class="file-date" x-text="$store.fileBrowser.formatDate(file.modified)"></div>
50 <div class="file-actions">
51 - <button class="action-button download-button" @click.stop="$store.fileBrowser.downloadFile(file)">
52 - <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 19.5 19.5"><path d="m.75,14.25v2.25c0,1.24,1.01,2.25,2.25,2.25h13.5c1.24,0,2.25-1.01,2.25-2.25v-2.25m-4.5-4.5l-4.5,4.5m0,0l-4.5-4.5m4.5,4.5V.75" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"/></svg>
51 + <button class="btn-icon-action" @click.stop="$store.fileBrowser.downloadFile(file)" title="Download file">
52 + <span class="material-symbols-outlined">download</span>
53 </button>
54 - <button class="delete-button" @click.stop="$store.fileBrowser.deleteFile(file)">
55 - <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 15.03 22.53" fill="currentColor"><path d="m14.55,7.82H4.68L14.09,3.19c.83-.41,1.17-1.42.77-2.25-.41-.83-1.42-1.17-2.25-.77l-3.16,1.55-.15-.31c-.22-.44-.59-.76-1.05-.92-.46-.16-.96-.13-1.39.09l-2.08,1.02c-.9.44-1.28,1.54-.83,2.44l.15.31-3.16,1.55c-.83.41-1.17,1.42-.77,2.25.29.59.89.94,1.51.94.25,0,.5-.06.74-.17l.38-.19s.09.03.14.03h11.14v11.43c0,.76-.62,1.38-1.38,1.38h-.46v-11.28c0-.26-.21-.47-.47-.47s-.47.21-.47.47v11.28h-2.39v-11.28c0-.26-.21-.47-.47-.47s-.47.21-.47.47v11.28h-2.39v-11.28c0-.26-.21-.47-.47-.47s-.47.21-.47.47v11.28h-.46c-.76,0-1.38-.62-1.38-1.38v-9.9c0-.26-.21-.47-.47-.47s-.47.21-.47.47v9.9c0,1.28,1.04,2.32,2.32,2.32h8.55c1.28,0,2.32-1.04,2.32-2.32v-11.91c0-.26-.21-.47-.47-.47Z" stroke-width="0"/></svg>
54 + <button class="btn-icon-action" @click.stop="$confirmClick($event, () => $store.fileBrowser.deleteFile(file))" title="Delete file">
55 + <span class="material-symbols-outlined">delete</span>
56 </button>
57 </div>
58 </div>
@@ -279,47 +279,12 @@
279 .btn-upload:active {
280 background-color: #2b309c;
281 }
282 - /* Delete Button Styles */
283 - .delete-button {
284 - background: none;
285 - border: none;
286 - color: var(--color-primary);
287 - cursor: pointer;
288 - width: 32px;
289 - padding: 4px 8px;
290 - border-radius: 4px;
291 - transition: opacity 0.2s, background-color 0.2s;
292 - }
293 - .delete-button:hover {
294 - color: #ff7878;
295 - }
296 - .delete-button:active {
297 - opacity: 0.6;
298 - }
282
283 /* File Actions */
284 .file-actions {
285 display: flex;
286 gap: var(--spacing-xs);
287 }
305 - .action-button {
306 - background: none;
307 - border: none;
308 - cursor: pointer;
309 - width: 32px;
310 - padding: 6px 8px;
311 - border-radius: 4px;
312 - transition: background-color 0.2s;
313 - }
314 - .download-button {
315 - color: var(--color-primary);
316 - }
317 - .download-button:hover {
318 - background-color: var(--color-border);
319 - }
320 - .light-mode .download-button:hover {
321 - background-color: #c6d4de;
322 - }
288 /* Responsive Design */
289 @media (max-width: 768px) {
290 .file-header,
webui/components/modals/memory/memory-dashboard-store.js
-11
@@ -324,9 +324,6 @@ const memoryDashboardStore = {
324 const selectedMemories = this.selectedMemories;
325 if (selectedMemories.length === 0) return;
326
327 - const confirmMessage = `Are you sure you want to delete ${selectedMemories.length} selected memories? This cannot be undone.`;
328 - if (!confirm(confirmMessage)) return;
329 -
327 try {
328 this.loading = true;
329 const response = await API.callJsonApi("memory_dashboard", {
@@ -554,14 +551,6 @@ ${memory.content_full}
551 },
552
553 async deleteMemory(memory) {
557 - if (
558 - !confirm(
559 - `Are you sure you want to delete this memory from ${memory.area}?`
560 - )
561 - ) {
562 - return;
563 - }
564 -
554 try {
555 // Check if this is the memory currently being viewed in detail modal
556 const isViewingThisMemory =
webui/components/modals/memory/memory-dashboard.html
+3 -3
@@ -149,7 +149,7 @@
149 </button>
150
151 <button class="btn btn-mass delete"
152 - @click="$store.memoryDashboardStore.bulkDeleteMemories()"
152 + @click="$confirmClick($event, () => $store.memoryDashboardStore.bulkDeleteMemories())"
153 title="Delete Selected Memories">
154 <span class="material-symbols-outlined">delete</span>
155 Delete
@@ -236,7 +236,7 @@
236 </button>
237
238 <button class="btn btn-action delete"
239 - @click="$store.memoryDashboardStore.deleteMemory(memory)"
239 + @click="$confirmClick($event, () => $store.memoryDashboardStore.deleteMemory(memory))"
240 title="Delete Memory">
241 <span class="material-symbols-outlined">delete</span>
242 </button>
@@ -272,7 +272,7 @@
272 </button>
273
274 <button class="btn btn-mass delete"
275 - @click="$store.memoryDashboardStore.bulkDeleteMemories()"
275 + @click="$confirmClick($event, () => $store.memoryDashboardStore.bulkDeleteMemories())"
276 title="Delete Selected Memories">
277 <span class="material-symbols-outlined">delete</span>
278 Delete
webui/components/modals/memory/memory-detail-modal.html
+1 -1
@@ -35,7 +35,7 @@
35 title="Edit Memory">
36 <span class="material-symbols-outlined">edit_document</span>
37 </button>
38 - <button class="btn btn-action-header delete danger" @click="$store.memoryDashboardStore.deleteMemory($store.memoryDashboardStore.detailMemory)"
38 + <button class="btn btn-action-header delete danger" @click="$confirmClick($event, () => $store.memoryDashboardStore.deleteMemory($store.memoryDashboardStore.detailMemory))"
39 title="Delete Memory">
40 <span class="material-symbols-outlined">delete</span>
41 </button>
webui/components/modals/scheduler/scheduler-modal.html
+1 -1
@@ -25,7 +25,7 @@
25 <div class="buttons-left">
26 <button type="button" class="button cancel"
27 x-show="$store.schedulerStore.isEditing"
28 - @click="$store.schedulerStore.deleteTask($store.schedulerStore.editingTask.uuid)">Delete</button>
28 + @click="$confirmClick($event, () => $store.schedulerStore.deleteTask($store.schedulerStore.editingTask.uuid))">Delete</button>
29 </div>
30 <div class="buttons-right">
31 <button type="button" class="button cancel"
webui/components/modals/scheduler/scheduler-store.js
-8
@@ -721,14 +721,6 @@ const schedulerStoreModel = {
721 },
722
723 async deleteTask(taskId) {
724 - if (
725 - !window.confirm(
726 - "Are you sure you want to delete this task? This action cannot be undone."
727 - )
728 - ) {
729 - return;
730 - }
731 -
724 try {
725 if (typeof chatsStore.switchFromContext === "function") {
726 await chatsStore.switchFromContext(taskId);
webui/components/modals/scheduler/scheduler-task-detail.html
+2 -2
@@ -21,7 +21,7 @@
21 <div class="buttons-container scheduler-detail-actions">
22 <div class="buttons-left">
23 <button type="button" class="button cancel"
24 - @click="$store.schedulerStore.deleteFromDetail()">Delete</button>
24 + @click="$confirmClick($event, () => $store.schedulerStore.deleteFromDetail())">Delete</button>
25 </div>
26 <div class="buttons-right">
27 <button type="button" class="button confirm"
@@ -145,7 +145,7 @@
145 <div class="buttons-container scheduler-detail-footer-actions">
146 <div class="buttons-left">
147 <button type="button" class="button cancel"
148 - @click="$store.schedulerStore.deleteFromDetail()">Delete</button>
148 + @click="$confirmClick($event, () => $store.schedulerStore.deleteFromDetail())">Delete</button>
149 </div>
150 <div class="buttons-right">
151 <button type="button" class="button confirm"
webui/components/modals/scheduler/scheduler-task-editor.html
+1 -1
@@ -223,7 +223,7 @@
223 <div class="buttons-container scheduler-editor-actions scheduler-editor-actions-top">
224 <div class="buttons-left">
225 <button type="button" class="button cancel"
226 - @click="$store.schedulerStore.deleteTask($store.schedulerStore.editingTask.uuid)">Delete</button>
226 + @click="$confirmClick($event, () => $store.schedulerStore.deleteTask($store.schedulerStore.editingTask.uuid))">Delete</button>
227 </div>
228 <div class="buttons-right">
229 <button type="button" class="button cancel"
webui/components/modals/scheduler/scheduler-task-list.html
+1 -1
@@ -146,7 +146,7 @@
146 <span class="material-symbols-outlined">edit</span>
147 </button>
148 <button class="btn btn-action delete"
149 - @click="$store.schedulerStore.deleteTask(task.uuid)" title="Delete Task">
149 + @click="$confirmClick($event, () => $store.schedulerStore.deleteTask(task.uuid))" title="Delete Task">
150 <span class="material-symbols-outlined">delete</span>
151 </button>
152 </div>
webui/components/projects/project-edit.html
+2 -2
@@ -16,7 +16,7 @@
16 <div class="buttons-container" style="margin: var(--spacing-md) var(--spacing-sm) var(--spacing-lg) var(--spacing-sm);">
17 <div class="buttons-left">
18 <button type="button" class="button cancel"
19 - @click="$store.projects.deleteProjectAndCloseModal()">Delete</button>
19 + @click="$confirmClick($event, () => $store.projects.deleteProjectAndCloseModal())">Delete</button>
20 </div>
21 <div class="buttons-right">
22 <button type="button" class="button cancel"
@@ -69,7 +69,7 @@
69 <div class="buttons-container" style="margin: var(--spacing-md) var(--spacing-sm) var(--spacing-lg) var(--spacing-sm);">
70 <div class="buttons-left">
71 <button type="button" class="button cancel"
72 - @click="$store.projects.deleteProjectAndCloseModal()">Delete</button>
72 + @click="$confirmClick($event, () => $store.projects.deleteProjectAndCloseModal())">Delete</button>
73 </div>
74 <div class="buttons-right">
75 <button type="button" class="button cancel"
webui/components/projects/project-list.html
+1 -1
@@ -59,7 +59,7 @@
59 <button type="button" class="button icon-button" title="Edit" @click="$store.projects.openEditModal(project.name)">
60 <span class="icon material-symbols-outlined">edit</span>
61 </button>
62 - <button type="button" class="button cancel icon-button" title="Delete" @click="$store.projects.deleteProject(project.name)">
62 + <button type="button" class="button cancel icon-button" title="Delete" @click="$confirmClick($event, () => $store.projects.deleteProject(project.name))">
63 <span class="icon material-symbols-outlined">delete</span>
64 </button>
65 </div>
webui/components/sidebar/chats/chats-list.html
+4 -21
@@ -33,7 +33,9 @@
33 <span class="chat-name" :title="context.name ? context.name : 'Chat #' + context.no"
34 x-text="context.name ? context.name : 'Chat #' + context.no"></span>
35 </div>
36 - <button class="edit-button" @click.stop="$store.chats.killChat(context.id)">X</button>
36 + <button class="btn-icon-action" title="Close chat" @click.stop="$confirmClick($event, () => $store.chats.killChat(context.id))">
37 + <span class="material-symbols-outlined">close</span>
38 + </button>
39 </div>
40 </li>
41 </template>
@@ -128,28 +130,9 @@
130 font-size: var(--font-size-small);
131 }
132
131 - .edit-button {
133 + .chat-container .btn-icon-action {
134 flex-shrink: 0;
135 margin-right: 8px;
134 - background-color: transparent;
135 - border: 1px solid var(--color-border);
136 - border-radius: 0.1875rem;
137 - color: var(--color-primary);
138 - cursor: pointer;
139 - padding: 0.125rem 0.5rem;
140 - transition: all var(--transition-speed) ease-in-out;
141 - width: 2rem;
142 - height: 2rem;
143 - }
144 -
145 - .edit-button:hover {
146 - border-color: var(--color-primary);
147 - background-color: #32455690;
148 - }
149 -
150 - .edit-button:active {
151 - background-color: #131a2090;
152 - color: rgba(253, 253, 253, 0.35);
136 }
137
138 .empty-list-message {
webui/components/sidebar/tasks/tasks-list.html
+15 -14
@@ -38,20 +38,20 @@
38 <span class="scheduler-status-badge scheduler-status-badge-small"
39 :class="task.state ? `scheduler-status-${task.state}` : 'scheduler-status-idle'"
40 x-text="task.state || 'idle'"></span>
41 - <button class="edit-button"
42 - @click.stop="$store.tasks.openDetail(task.id)"
43 - title="View task details" style="margin-left: auto; margin-right: 5px;">
44 - <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 512 512" fill="var(--color-primary)" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
45 - <path d="M256 0c70.69 0 134.69 28.66 181.02 74.98C483.34 121.3 512 185.31 512 256c0 70.69-28.66 134.7-74.98 181.02C390.69 483.34 326.69 512 256 512c-70.69 0-134.69-28.66-181.02-74.98C28.66 390.69 0 326.69 0 256c0-70.69 28.66-134.69 74.98-181.02C121.31 28.66 185.31 0 256 0zm-9.96 161.03c0-4.28.76-8.26 2.27-11.91 1.5-3.63 3.77-6.94 6.79-9.91 3-2.95 6.29-5.2 9.84-6.7 3.57-1.5 7.41-2.28 11.52-2.28 4.12 0 7.96.78 11.49 2.27 3.54 1.51 6.78 3.76 9.75 6.73 2.95 2.97 5.16 6.26 6.64 9.91 1.49 3.63 2.22 7.61 2.22 11.89 0 4.17-.73 8.08-2.21 11.69-1.48 3.6-3.68 6.94-6.65 9.97-2.94 3.03-6.18 5.32-9.72 6.84-3.54 1.51-7.38 2.29-11.52 2.29-4.22 0-8.14-.76-11.75-2.26-3.58-1.51-6.86-3.79-9.83-6.79-2.94-3.02-5.16-6.34-6.63-9.97-1.48-3.62-2.21-7.54-2.21-11.77zm13.4 178.16c-1.11 3.97-3.35 11.76 3.3 11.76 1.44 0 3.27-.81 5.46-2.4 2.37-1.71 5.09-4.31 8.13-7.75 3.09-3.5 6.32-7.65 9.67-12.42 3.33-4.76 6.84-10.22 10.49-16.31.37-.65 1.23-.87 1.89-.48l12.36 9.18c.6.43.73 1.25.35 1.86-5.69 9.88-11.44 18.51-17.26 25.88-5.85 7.41-11.79 13.57-17.8 18.43l-.1.06c-6.02 4.88-12.19 8.55-18.51 11.01-17.58 6.81-45.36 5.7-53.32-14.83-5.02-12.96-.9-27.69 3.06-40.37l19.96-60.44c1.28-4.58 2.89-9.62 3.47-14.33.97-7.87-2.49-12.96-11.06-12.96h-17.45c-.76 0-1.38-.62-1.38-1.38l.08-.48 4.58-16.68c.16-.62.73-1.04 1.35-1.02l89.12-2.79c.76-.03 1.41.57 1.44 1.33l-.07.43-37.76 124.7zm158.3-244.93c-41.39-41.39-98.58-67-161.74-67-63.16 0-120.35 25.61-161.74 67-41.39 41.39-67 98.58-67 161.74 0 63.16 25.61 120.35 67 161.74 41.39 41.39 98.58 67 161.74 67 63.16 0 120.35-25.61 161.74-67 41.39-41.39 67-98.58 67-161.74 0-63.16-25.61-120.35-67-161.74z"/>
46 - </svg>
47 - </button>
48 - <button class="edit-button" title="Clear task chat"
49 - @click.stop="$store.tasks.reset(task.id)" style="margin-right: 5px;">
50 - <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 122.88 121.1" fill="var(--color-primary)" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
51 - <path d="M62.89,56.03c1.11-0.35,2.34-0.25,3.72,0.37l10.4,7.87c2.26,1.71,4.24,3.78,2.73,6.9 c-0.51,1.06-1.4,2.1-2.38,3.49l-0.53,0.75c-1.97,2.8-2.61,2-5.71,1.83c0.56,13.37,1.75,27.82-2.64,40.88 c-0.87,2.7-3.32,3.44-6.95,2.71l-6.1-2.03c4.11-6.14,6.16-13.85,6.44-22.89c-3.46,8.58-6.8,16.96-10.68,20.86l-6.28-2.08 c0.61-3.05,1.05-5.43,0.35-6.9l-4.07,4.24l-9.33-5.77c6.36-3.36,11.62-7.87,15.6-13.73c-6.69,5.01-12.76,8.1-18.14,8.99 c-2.75,0.83-4.49,0.35-5.16-1.53c-0.48-1.34-0.05-1.77,0.81-2.86c1.11-1.41,2.61-2.67,4.35-3.79c-3.13,1.1-4.64,0.95-6.37,1.51 c-4.9,1.59-9.94-1.86-8.26-6.9c1.07-3.23,3.54-3.09,6.67-4.07l5.42-1.69c-5.19,0.28-10.32,0.45-15.02-0.25 c-5.4-0.8-5.31-0.99-8.24-5.38c-3.94-5.91-6.25-11.45,2.52-9.16c16.73,3.18,33.56,5.34,51.25-0.98c-0.76-1.32-0.9-2.57-0.5-3.73 C57.37,60.94,61.13,56.58,62.89,56.03L62.89,56.03z M113.8,2.42L74.45,51.53c-4.71,6.68,3.2,11.91,8.39,5.64l39.2-49.27 C125.12,1.86,119.13-3.16,113.8,2.42L113.8,2.42z"/>
52 - </svg>
53 - </button>
54 - <button class="edit-button" title="Delete task" @click.stop="$store.tasks.deleteTask(task.id)">X</button>
41 + <div class="task-actions">
42 + <button class="btn-icon-action"
43 + @click.stop="$store.tasks.openDetail(task.id)"
44 + title="View task details">
45 + <span class="material-symbols-outlined">Info</span>
46 + </button>
47 + <button class="btn-icon-action" title="Clear task chat"
48 + @click.stop="$store.tasks.reset(task.id)">
49 + <span class="material-symbols-outlined">Refresh</span>
50 + </button>
51 + <button class="btn-icon-action" title="Delete task" @click.stop="$confirmClick($event, () => $store.tasks.deleteTask(task.id))">
52 + <span class="material-symbols-outlined">Close</span>
53 + </button>
54 + </div>
55 </div>
56 </div>
57 </div>
@@ -138,6 +138,7 @@
138 .light-mode .task-name:hover { background-color: rgba(0,0,0,0.05); }
139
140 .task-info-line { display: flex; justify-content: space-between; align-items: center; width: 100%; margin-top: 2px; }
141 + .task-actions { display: flex; gap: 0.375rem; margin-left: auto; }
142
143 .task-container {
144 width: 100%;
webui/css/buttons.css
+48
@@ -177,3 +177,51 @@
177 align-items: center;
178 gap: 0.25rem;
179 }
180 +
181 +/* Inline Confirmation State */
182 +.confirming {
183 + border-color: var(--color-highlight) !important;
184 + background-color: var(--color-highlight) !important;
185 + color: #fff !important;
186 +}
187 +
188 +.confirming:hover {
189 + filter: brightness(1.1);
190 +}
191 +
192 +/* Standard icon-only action button for destructive actions */
193 +.btn-icon-action {
194 + display: inline-flex;
195 + align-items: center;
196 + justify-content: center;
197 + background: transparent;
198 + border: 1px solid var(--color-border);
199 + border-radius: 0.25rem;
200 + color: var(--color-primary);
201 + cursor: pointer;
202 + padding: 0.25rem;
203 + width: 1.75rem;
204 + height: 1.75rem;
205 + transition: all 0.18s cubic-bezier(0.4, 0, 0.2, 1);
206 + flex-shrink: 0;
207 +}
208 +
209 +.btn-icon-action .material-symbols-outlined,
210 +.btn-icon-action .material-icons-outlined {
211 + font-size: 1rem;
212 +}
213 +
214 +.btn-icon-action:hover {
215 + border-color: var(--color-primary);
216 + background-color: rgba(50, 69, 86, 0.56);
217 +}
218 +
219 +.btn-icon-action:active {
220 + background-color: rgba(19, 26, 32, 0.56);
221 + color: rgba(253, 253, 253, 0.35);
222 +}
223 +
224 +.btn-icon-action.danger:hover {
225 + border-color: #e57373;
226 + color: #e57373;
227 +}
webui/js/confirmClick.js new
+74
@@ -0,0 +1,74 @@
1 +// Inline button two-click confirmation for destructive actions.
2 +// First click arms, second click confirms, timeout resets.
3 +
4 +const CONFIRM_TIMEOUT = 2000;
5 +const CONFIRM_CLASS = 'confirming';
6 +const CONFIRM_ICON = 'check';
7 +const CONFIRM_TEXT = 'Confirm';
8 +
9 +const buttonStates = new WeakMap();
10 +
11 +// Handles inline two-click confirmation for a button.
12 +export function confirmClick(event, action) {
13 + const button = event.currentTarget;
14 + if (!button) return;
15 +
16 + const state = buttonStates.get(button);
17 +
18 + if (state?.confirming) {
19 + clearTimeout(state.timeoutId);
20 + resetButton(button, state);
21 + buttonStates.delete(button);
22 + action();
23 + } else {
24 + const iconEl = button.querySelector('.material-symbols-outlined, .material-icons-outlined');
25 + const isIconButton = iconEl && button.textContent.trim() === iconEl.textContent.trim();
26 +
27 + const newState = {
28 + confirming: true,
29 + isIconButton,
30 + originalIcon: iconEl?.textContent?.trim(),
31 + originalHTML: isIconButton ? null : button.innerHTML,
32 + timeoutId: setTimeout(() => {
33 + resetButton(button, newState);
34 + buttonStates.delete(button);
35 + }, CONFIRM_TIMEOUT)
36 + };
37 +
38 + buttonStates.set(button, newState);
39 + button.classList.add(CONFIRM_CLASS);
40 +
41 + if (isIconButton && iconEl) {
42 + // Icon-only button: just swap icon
43 + iconEl.textContent = CONFIRM_ICON;
44 + } else {
45 + // Text button: show icon + optional "Confirm" text
46 + const originalText = button.textContent.trim();
47 + const confirmContent = originalText.length >= 4
48 + ? `<span class="material-symbols-outlined">${CONFIRM_ICON}</span>${CONFIRM_TEXT}`
49 + : `<span class="material-symbols-outlined">${CONFIRM_ICON}</span>`;
50 + button.innerHTML = confirmContent;
51 + }
52 + }
53 +}
54 +
55 +// Reset button to original state
56 +function resetButton(button, state) {
57 + button.classList.remove(CONFIRM_CLASS);
58 + if (state.isIconButton) {
59 + const iconEl = button.querySelector('.material-symbols-outlined, .material-icons-outlined');
60 + if (iconEl && state.originalIcon) {
61 + iconEl.textContent = state.originalIcon;
62 + }
63 + } else if (state.originalHTML) {
64 + button.innerHTML = state.originalHTML;
65 + }
66 +}
67 +
68 +// Register Alpine magic helper
69 +export function registerAlpineMagic() {
70 + if (globalThis.Alpine) {
71 + Alpine.magic('confirmClick', () => confirmClick);
72 + }
73 +}
74 +
webui/js/initFw.js
+4
@@ -1,6 +1,7 @@
1 import * as initializer from "./initializer.js";
2 import * as _modals from "./modals.js";
3 import * as _components from "./components.js";
4 +import { registerAlpineMagic } from "./confirmClick.js";
5
6 // initialize required elements
7 await initializer.initialize();
@@ -8,6 +9,9 @@ await initializer.initialize();
9 // import alpine library
10 await import("../vendor/alpine/alpine.min.js");
11
12 +// register $confirmClick magic helper for inline button confirmations
13 +registerAlpineMagic();
14 +
15 // add x-destroy directive to alpine
16 Alpine.directive(
17 "destroy",