From 29c40dd9f9866fe97206e83c75a0c650f3f1e47a Mon Sep 17 00:00:00 2001 From: SandObserver <260779319+SandObserver@users.noreply.github.com> Date: Sat, 17 Oct 2026 14:38:25 -0400 Subject: [PATCH] Open a widget link once when the widget has its own click handler --- ui/js/widget-toolbox.js | 3 +++ ui/test/system-summary-link-press.test.mjs | 12 ++++++--- ui/test/widget-toolbox-link.test.mjs | 30 ++++++++++++++++----- ui/widgets/backup/backup.html | 2 +- ui/widgets/books/index.html | 2 +- ui/widgets/clock/analog.html | 2 +- ui/widgets/clock/digital.html | 2 +- ui/widgets/connections/connections-map.html | 2 +- ui/widgets/connections/connections-vpn.html | 2 +- ui/widgets/dashboard-switch/index.html | 2 +- ui/widgets/disk-health/index.html | 2 +- ui/widgets/dns/index.html | 2 +- ui/widgets/github/contributions.html | 2 +- ui/widgets/github/pullrequests.html | 2 +- ui/widgets/nowplaying/index.html | 2 +- ui/widgets/system-summary/index.html | 4 +-- ui/widgets/weather/index.html | 2 +- 17 files changed, 51 insertions(+), 24 deletions(-) diff --git a/ui/js/widget-toolbox.js b/ui/js/widget-toolbox.js index e7d226dd..e0b6d612 100644 --- a/ui/js/widget-toolbox.js +++ b/ui/js/widget-toolbox.js @@ -120,6 +120,9 @@ export function openUrl(href) { a.href = href; a.target = '_blank'; a.rel = 'noopener noreferrer'; + /* Keep this. Without it, a click handler on the body opens the link + again, in a loop, and the browser blocks every tab. */ + a.addEventListener('click', e => e.stopPropagation()); document.body.appendChild(a); a.click(); a.remove(); diff --git a/ui/test/system-summary-link-press.test.mjs b/ui/test/system-summary-link-press.test.mjs index 0b4b7cc2..ddeb9af8 100644 --- a/ui/test/system-summary-link-press.test.mjs +++ b/ui/test/system-summary-link-press.test.mjs @@ -14,15 +14,17 @@ const pressOpensLink = new Function( )(); const holdToShow = lift('holdToShow', 'target, show'); -function node(classes = [], parent = null) { +function node(classes = [], parent = null, id = '') { const el = { parent, + id, classes: new Set(classes), classList: { add: c => el.classes.add(c) }, addEventListener() {}, closest(sel) { - const wanted = sel.split(',').map(s => s.trim().slice(1)); - for (let n = el; n; n = n.parent) if (wanted.some(c => n.classes.has(c))) return n; + const wanted = sel.split(',').map(s => s.trim()); + const hit = n => wanted.some(w => (w[0] === '#' ? n.id === w.slice(1) : n.classes.has(w.slice(1)))); + for (let n = el; n; n = n.parent) if (hit(n)) return n; return null; }, }; @@ -51,3 +53,7 @@ test('a press anywhere else opens the link', () => { test('the body click handler opens the link only through that check', () => { assert.match(src, /if \(target && pressOpensLink\(e\.target\)\) openUrl\(target\);/); }); + +test('a press on the link element is left to the link, so it opens once', () => { + assert.equal(pressOpensLink(node([], node(), 'ss-link')), false); +}); diff --git a/ui/test/widget-toolbox-link.test.mjs b/ui/test/widget-toolbox-link.test.mjs index e187c95a..01f06788 100644 --- a/ui/test/widget-toolbox-link.test.mjs +++ b/ui/test/widget-toolbox-link.test.mjs @@ -8,13 +8,21 @@ globalThis.location = { search: '?id=test' }; const { linkTo } = await import('../js/widget-toolbox.js'); let opened = []; +let bubbled = 0; globalThis.document = { - createElement: () => ({ - click() { - opened.push(this.href); - }, - remove() {}, - }), + createElement: () => { + const listeners = []; + return { + addEventListener: (type, fn) => type === 'click' && listeners.push(fn), + click() { + opened.push(this.href); + let stopped = false; + for (const fn of listeners) fn({ stopPropagation: () => (stopped = true) }); + if (!stopped) bubbled++; + }, + remove() {}, + }; + }, body: { appendChild() {} }, }; @@ -146,3 +154,13 @@ test('the contributions summary uses the yearly total', () => { /const total = formatNumber\(Math\.max\(0, Number\(data\.totalContributions\) \|\| 0\)\);/, ); }); + +test('the click that opens a link never bubbles to a widget handler', () => { + opened = []; + bubbled = 0; + const el = fakeEl(); + linkTo(el, 'https://example.test/'); + el.fire('click', { target: el }); + assert.deepEqual(opened, ['https://example.test/']); + assert.equal(bubbled, 0); +}); diff --git a/ui/widgets/backup/backup.html b/ui/widgets/backup/backup.html index 7dab7323..d853a244 100644 --- a/ui/widgets/backup/backup.html +++ b/ui/widgets/backup/backup.html @@ -122,7 +122,7 @@