|
17 | 17 | const createApi = (deps = {}) => { |
18 | 18 | const win = deps.window || fallbackWindow; |
19 | 19 | const doc = deps.document || win?.document || (typeof document !== 'undefined' ? document : null); |
| 20 | + const jq = deps.$ || win?.jQuery || win?.$; |
20 | 21 | const utils = deps.utils && typeof deps.utils === 'object' ? deps.utils : {}; |
21 | 22 | const escapeHtml = typeof deps.escapeHtml === 'function' |
22 | 23 | ? deps.escapeHtml |
|
132 | 133 | const queueLoadlistRefresh = typeof deps.queueLoadlistRefresh === 'function' |
133 | 134 | ? deps.queueLoadlistRefresh |
134 | 135 | : (() => {}); |
| 136 | + const appendDockerPreviewActionButtons = typeof deps.appendDockerPreviewActionButtons === 'function' |
| 137 | + ? deps.appendDockerPreviewActionButtons |
| 138 | + : (() => {}); |
135 | 139 |
|
136 | 140 | let rootNode = null; |
137 | 141 | let clickHandler = null; |
138 | | - let contextMenuHandler = null; |
139 | 142 | let renderToken = 0; |
140 | 143 |
|
141 | 144 | const getHostTable = () => doc?.querySelector('table#docker_containers') || null; |
|
192 | 195 | if (clickHandler && rootNode) { |
193 | 196 | rootNode.removeEventListener('click', clickHandler); |
194 | 197 | } |
195 | | - if (contextMenuHandler && rootNode) { |
196 | | - rootNode.removeEventListener('contextmenu', contextMenuHandler); |
197 | | - } |
198 | 198 | clickHandler = null; |
199 | | - contextMenuHandler = null; |
200 | 199 | if (rootNode && rootNode.parentNode) { |
201 | 200 | rootNode.parentNode.removeChild(rootNode); |
202 | 201 | } |
|
274 | 273 | return false; |
275 | 274 | } |
276 | 275 | const safeType = eventType === 'contextmenu' ? 'contextmenu' : 'click'; |
| 276 | + if (typeof jq === 'function') { |
| 277 | + try { |
| 278 | + jq(trigger).trigger(safeType); |
| 279 | + return true; |
| 280 | + } catch (_error) { |
| 281 | + // Fall through to native DOM dispatch. |
| 282 | + } |
| 283 | + } |
| 284 | + if (safeType === 'click' && typeof trigger.click === 'function') { |
| 285 | + try { |
| 286 | + trigger.click(); |
| 287 | + return true; |
| 288 | + } catch (_error) { |
| 289 | + // Fall through to synthetic DOM dispatch. |
| 290 | + } |
| 291 | + } |
277 | 292 | const event = new MouseEvent(safeType, { |
278 | 293 | bubbles: true, |
279 | 294 | cancelable: true, |
280 | 295 | view: win || undefined, |
281 | 296 | button: safeType === 'contextmenu' ? 2 : 0, |
282 | 297 | buttons: safeType === 'contextmenu' ? 2 : 1 |
283 | 298 | }); |
284 | | - return trigger.dispatchEvent(event); |
| 299 | + try { |
| 300 | + return trigger.dispatchEvent(event); |
| 301 | + } catch (_error) { |
| 302 | + return false; |
| 303 | + } |
285 | 304 | }; |
286 | 305 |
|
287 | 306 | const hydrateNativeMemberSurface = (surface, containerName) => { |
|
293 | 312 | return; |
294 | 313 | } |
295 | 314 | const trigger = getNativeMemberTrigger(safeName); |
296 | | - if (!(trigger instanceof HTMLElement)) { |
297 | | - surface.addEventListener('click', (event) => { |
298 | | - event.preventDefault(); |
299 | | - event.stopPropagation(); |
300 | | - proxyNativeMemberTrigger(safeName, 'click'); |
301 | | - }); |
302 | | - surface.addEventListener('contextmenu', (event) => { |
303 | | - event.preventDefault(); |
304 | | - event.stopPropagation(); |
305 | | - proxyNativeMemberTrigger(safeName, 'contextmenu'); |
306 | | - }); |
307 | | - return; |
308 | | - } |
309 | | - const inlineClick = String(trigger.getAttribute('onclick') || '').trim(); |
310 | | - const inlineContextMenu = String(trigger.getAttribute('oncontextmenu') || '').trim(); |
311 | | - const title = String(trigger.getAttribute('title') || '').trim(); |
312 | 315 | surface.classList.add('hand'); |
313 | | - if (inlineClick) { |
314 | | - surface.setAttribute('onclick', inlineClick); |
315 | | - } else { |
316 | | - surface.addEventListener('click', (event) => { |
317 | | - event.preventDefault(); |
318 | | - event.stopPropagation(); |
319 | | - proxyNativeMemberTrigger(safeName, 'click'); |
320 | | - }); |
321 | | - } |
322 | | - if (inlineContextMenu) { |
323 | | - surface.setAttribute('oncontextmenu', inlineContextMenu); |
324 | | - } else { |
325 | | - surface.addEventListener('contextmenu', (event) => { |
326 | | - event.preventDefault(); |
327 | | - event.stopPropagation(); |
328 | | - proxyNativeMemberTrigger(safeName, 'contextmenu'); |
329 | | - }); |
330 | | - } |
| 316 | + surface.setAttribute('role', 'button'); |
| 317 | + surface.setAttribute('tabindex', '0'); |
| 318 | + const title = trigger instanceof HTMLElement ? String(trigger.getAttribute('title') || '').trim() : ''; |
331 | 319 | if (title) { |
332 | 320 | surface.setAttribute('title', title); |
333 | 321 | } |
| 322 | + surface.addEventListener('click', (event) => { |
| 323 | + event.preventDefault(); |
| 324 | + event.stopPropagation(); |
| 325 | + proxyNativeMemberTrigger(safeName, 'click'); |
| 326 | + }); |
| 327 | + surface.addEventListener('contextmenu', (event) => { |
| 328 | + event.preventDefault(); |
| 329 | + event.stopPropagation(); |
| 330 | + proxyNativeMemberTrigger(safeName, 'contextmenu'); |
| 331 | + }); |
| 332 | + surface.addEventListener('keydown', (event) => { |
| 333 | + if (event.key !== 'Enter' && event.key !== ' ') { |
| 334 | + return; |
| 335 | + } |
| 336 | + event.preventDefault(); |
| 337 | + event.stopPropagation(); |
| 338 | + proxyNativeMemberTrigger(safeName, 'click'); |
| 339 | + }); |
334 | 340 | }; |
335 | 341 |
|
336 | 342 | const computeOrderedFolderIds = (folders, prefs, hostOrder, unraidOrder) => { |
|
490 | 496 | ${member.updateReady ? '<span class="fv-docker-command-member-update">update ready</span>' : ''} |
491 | 497 | </span> |
492 | 498 | </span> |
493 | | - <span class="fv-docker-command-member-actions"> |
494 | | - ${member.webuiUrl ? `<span class="folder-element-custom-btn folder-element-webui"><a href="${escapeHtml(member.webuiUrl)}" title="Open WebUI" aria-label="Open WebUI" data-fv-command-member-action="webui" data-member-name="${escapeHtml(member.name)}"><i class="fa fa-globe" aria-hidden="true"></i></a></span>` : ''} |
495 | | - <span class="folder-element-custom-btn folder-element-console"><a href="#" title="Open console" aria-label="Open console" data-fv-command-member-action="console" data-member-name="${escapeHtml(member.name)}"><i class="fa fa-terminal" aria-hidden="true"></i></a></span> |
496 | | - <span class="folder-element-custom-btn folder-element-logs"><a href="#" title="Open logs" aria-label="Open logs" data-fv-command-member-action="logs" data-member-name="${escapeHtml(member.name)}"><i class="fa fa-bars" aria-hidden="true"></i></a></span> |
497 | | - </span> |
498 | 499 | </div> |
| 500 | + <span class="fv-docker-command-member-actions fv-preview-actions-compact" data-fv-command-member-actions-host="true" data-member-name="${escapeHtml(member.name)}" data-member-webui-url="${escapeHtml(member.webuiUrl)}" data-member-shell="${escapeHtml(member.shell)}"></span> |
499 | 501 | </div> |
500 | 502 | `).join(''); |
501 | 503 | return ` |
|
547 | 549 | } |
548 | 550 | hydrateNativeMemberSurface(surface, String(surface.getAttribute('data-member-name') || '').trim()); |
549 | 551 | }); |
| 552 | + if (typeof jq === 'function') { |
| 553 | + root.querySelectorAll('[data-fv-command-member-actions-host="true"]').forEach((host) => { |
| 554 | + if (!(host instanceof HTMLElement)) { |
| 555 | + return; |
| 556 | + } |
| 557 | + const memberName = String(host.getAttribute('data-member-name') || '').trim(); |
| 558 | + const memberWebuiUrl = String(host.getAttribute('data-member-webui-url') || '').trim(); |
| 559 | + const memberShell = String(host.getAttribute('data-member-shell') || '').trim() || '/bin/sh'; |
| 560 | + if (!memberName) { |
| 561 | + return; |
| 562 | + } |
| 563 | + appendDockerPreviewActionButtons( |
| 564 | + jq(host), |
| 565 | + { |
| 566 | + preview_webui: Boolean(memberWebuiUrl), |
| 567 | + preview_console: true, |
| 568 | + preview_logs: true |
| 569 | + }, |
| 570 | + memberName, |
| 571 | + memberShell, |
| 572 | + memberWebuiUrl |
| 573 | + ); |
| 574 | + }); |
| 575 | + } |
550 | 576 | return true; |
551 | 577 | }; |
552 | 578 |
|
|
558 | 584 | rootNode.removeEventListener('click', clickHandler); |
559 | 585 | } |
560 | 586 | clickHandler = (event) => { |
561 | | - const memberButton = event.target instanceof Element |
562 | | - ? event.target.closest('[data-fv-command-member-action]') |
563 | | - : null; |
564 | | - if (memberButton instanceof HTMLElement) { |
565 | | - event.preventDefault(); |
566 | | - event.stopPropagation(); |
567 | | - const memberAction = String(memberButton.getAttribute('data-fv-command-member-action') || '').trim(); |
568 | | - const memberTile = memberButton.closest('.fv-docker-command-member-tile'); |
569 | | - const folderCard = memberButton.closest('[data-folder-id]'); |
570 | | - const folderId = folderCard instanceof HTMLElement ? String(folderCard.getAttribute('data-folder-id') || '').trim() : ''; |
571 | | - const memberName = memberTile instanceof HTMLElement ? String(memberTile.getAttribute('data-member-name') || '').trim() : ''; |
572 | | - const memberWebuiUrl = memberTile instanceof HTMLElement ? String(memberTile.getAttribute('data-member-webui-url') || '').trim() : ''; |
573 | | - const memberShell = memberTile instanceof HTMLElement ? String(memberTile.getAttribute('data-member-shell') || '').trim() || '/bin/sh' : '/bin/sh'; |
574 | | - if (!folderId || !memberName) { |
575 | | - return; |
576 | | - } |
577 | | - if (memberAction === 'webui') { |
578 | | - openWebuiInNewTab(memberWebuiUrl); |
579 | | - return; |
580 | | - } |
581 | | - if (memberAction === 'console') { |
582 | | - openTerminal('docker', memberName, memberShell); |
583 | | - return; |
584 | | - } |
585 | | - if (memberAction === 'logs') { |
586 | | - openTerminal('docker', memberName, '.log'); |
587 | | - return; |
588 | | - } |
589 | | - } |
590 | 587 | const button = event.target instanceof Element |
591 | 588 | ? event.target.closest('[data-fv-command-action]') |
592 | 589 | : null; |
|
0 commit comments