Skip to content

Commit b0fdeaf

Browse files
bmmmmclaude
andcommitted
Fix 5 bugs and add Playwright cache to CI
Bugs fixed: - aria-expanded now returns string "true"/"false" instead of boolean - touchend handler guards against empty changedTouches - HTML escaping now covers >, " (XSS prevention in event titles) - FundingStatus listeners only bind once (prevents memory leak on re-render) - showLastSync interval pauses when tab is hidden, resumes on focus CI improvement: - Cache Playwright browsers and npm deps in deploy workflow - Skip browser download on cache hit (only install system deps) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent f4526cf commit b0fdeaf

3 files changed

Lines changed: 43 additions & 15 deletions

File tree

.github/workflows/deploy.yml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ jobs:
2121
- uses: actions/setup-node@v6
2222
with:
2323
node-version: "22"
24+
cache: "npm"
2425

2526
- name: Install dependencies
2627
run: npm ci
@@ -33,9 +34,21 @@ jobs:
3334
exit 1
3435
)
3536
37+
- name: Cache Playwright browsers
38+
id: pw-cache
39+
uses: actions/cache@v4
40+
with:
41+
path: ~/.cache/ms-playwright
42+
key: playwright-${{ hashFiles('package-lock.json') }}
43+
3644
- name: Install Playwright browsers
45+
if: steps.pw-cache.outputs.cache-hit != 'true'
3746
run: npx playwright install --with-deps chromium
3847

48+
- name: Install Playwright system deps (cache hit)
49+
if: steps.pw-cache.outputs.cache-hit == 'true'
50+
run: npx playwright install-deps chromium
51+
3952
- name: Run sync-events unit tests
4053
run: node --test tests/sync-events.spec.mjs
4154

events.js

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -512,7 +512,18 @@
512512
}
513513

514514
render();
515-
setInterval(render, 30000);
515+
var intervalId = setInterval(render, 30000);
516+
517+
// Clean up when navigating away
518+
document.addEventListener("visibilitychange", function () {
519+
if (document.hidden) {
520+
clearInterval(intervalId);
521+
intervalId = null;
522+
} else if (!intervalId) {
523+
render();
524+
intervalId = setInterval(render, 30000);
525+
}
526+
});
516527
}
517528

518529
// ── Init ──────────────────────────────────────────────────────────────────

main.js

Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@
4343

4444
utils.addEventListenerSafe(menuToggle, "click", () => {
4545
const expanded = mainNav.classList.toggle("active");
46-
menuToggle.setAttribute("aria-expanded", expanded);
46+
menuToggle.setAttribute("aria-expanded", String(expanded));
4747
});
4848
},
4949
};
@@ -146,6 +146,7 @@
146146
});
147147

148148
utils.addEventListenerSafe(carousel, "touchend", (e) => {
149+
if (!e.changedTouches.length) return;
149150
const endX = e.changedTouches[0].clientX;
150151
const deltaX = this.state.startX - endX;
151152

@@ -351,6 +352,7 @@
351352
now.setHours(0, 0, 0, 0);
352353
const DAYS = ["SO", "MO", "DI", "MI", "DO", "FR", "SA"];
353354
const pad = (n) => (n < 10 ? "0" + n : "" + n);
355+
const escHtml = (s) => s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
354356

355357
const upcoming = events
356358
.filter((e) => new Date(e.date + "T23:59:59") >= now)
@@ -370,10 +372,10 @@
370372
html += '<span class="event-preview__date">' + date + "</span>";
371373
html +=
372374
'<span class="event-preview__title">' +
373-
e.title.replace(/&/g, "&amp;").replace(/</g, "&lt;");
375+
escHtml(e.title);
374376
if (e.source && e.source !== "bitcircus101") {
375377
html += ' <span class="event-preview__source">' +
376-
e.source.replace(/&/g, "&amp;") + "</span>";
378+
escHtml(e.source) + "</span>";
377379
}
378380
html += "</span>";
379381
if (e.time)
@@ -449,18 +451,20 @@
449451
'<a href="donations.html">$ unterst\u00fctzen \u2192</a>' +
450452
"</div>";
451453

452-
// Click to toggle info panel
453-
el.addEventListener("click", function (e) {
454-
e.stopPropagation();
455-
var info = el.querySelector(".footer__funding-info");
456-
if (info) info.classList.toggle("active");
457-
});
454+
// Click to toggle info panel (only bind once)
455+
if (!el._fundingBound) {
456+
el.addEventListener("click", function (e) {
457+
e.stopPropagation();
458+
var info = el.querySelector(".footer__funding-info");
459+
if (info) info.classList.toggle("active");
460+
});
458461

459-
// Close on click outside
460-
document.addEventListener("click", function () {
461-
var info = el.querySelector(".footer__funding-info");
462-
if (info) info.classList.remove("active");
463-
});
462+
document.addEventListener("click", function () {
463+
var info = el.querySelector(".footer__funding-info");
464+
if (info) info.classList.remove("active");
465+
});
466+
el._fundingBound = true;
467+
}
464468
},
465469
};
466470

0 commit comments

Comments
 (0)