@@ -51,10 +51,10 @@ const schedule = await getSchedule()
5151 <div class = " max-w-3xl mx-auto mb-6 flex justify-end" >
5252 <button
5353 type = " button"
54+ id = " favorites-filter-btn"
5455 class = " favorites-filter-btn inline-flex items-center gap-2 px-4 py-2 rounded-lg border border-white/10 text-sm text-pycon-gray-25 hover:border-pycon-orange/50 hover:text-pycon-orange transition-colors focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-pycon-yellow aria-pressed:bg-pycon-orange aria-pressed:text-pycon-black aria-pressed:font-semibold aria-pressed:border-pycon-orange"
5556 aria-pressed = " false"
5657 aria-label = { t .list .favoritesOnly }
57- data-filter-favorites
5858 data-filter-on-prefix = { t .list .filterOnPrefix }
5959 data-filter-on-singular = { t .list .filterOnSingular }
6060 data-filter-on-plural = { t .list .filterOnPlural }
@@ -151,6 +151,10 @@ const schedule = await getSchedule()
151151<script is:inline src =" https://pretalx.com/pycones-2026/widgets/schedule.js" ></script >
152152
153153<script is:inline >
154+ try {
155+ console.log('[programa] script starting')
156+ } catch (e) {}
157+
154158 ;(function () {
155159 // === View toggle (list <-> grid) ===
156160 ;(function () {
@@ -180,10 +184,6 @@ const schedule = await getSchedule()
180184
181185 try {
182186 const saved = localStorage.getItem(STORAGE_KEY)
183- // Only restore the saved view if it matches the HTML default. Legacy
184- // values (e.g. 'list' from when list was the default) are ignored so
185- // the page always opens with the current default and there's no jump
186- // from a stale saved value.
187187 const defaultChecked = document.querySelector('input[name="schedule-view"]:checked')
188188 const defaultValue = defaultChecked ? defaultChecked.value : null
189189 if ((saved === 'list' || saved === 'grid') && saved === defaultValue) {
@@ -192,11 +192,7 @@ const schedule = await getSchedule()
192192 } catch (e) {}
193193 })()
194194
195- // === Favorites sync (shared with the Pretalx widget) ===
196- // The widget persists favourites under `pycones-2026_favs` as a JSON
197- // array of session codes. We read/write the same key so toggling in
198- // the list is reflected in the widget (and vice-versa, next time the
199- // widget re-reads on mount).
195+ // === Favorites sync + filter (shared with the Pretalx widget) ===
200196 ;(function () {
201197 const FAV_KEY = 'pycones-2026_favs'
202198
@@ -247,59 +243,70 @@ const schedule = await getSchedule()
247243 empty.hidden = !(filterActive && count === 0)
248244 }
249245
250- // Click handler: handle both the per-session favourite button and
251- // the "only favourites" filter button. Event delegation on document
252- // so we don't depend on either button being in the DOM at script
253- // init time.
254- document.addEventListener('click', (e) => {
255- const target = e.target.closest && e.target.closest('button')
256- if (!target) return
257-
258- // Per-session favourite toggle
259- const favBtn = target.closest('.favorite-btn')
260- if (favBtn) {
261- e.preventDefault()
262- const code = favBtn.getAttribute('data-session-code')
263- if (!code) return
264- const favs = readFavs()
265- const idx = favs.indexOf(code)
266- let added
267- if (idx >= 0) {
268- favs.splice(idx, 1)
269- added = false
270- } else {
271- favs.push(code)
272- added = true
273- }
274- writeFavs(favs)
275- syncFavorites()
276- announce(added ? favBtn.dataset.addedMessage : favBtn.dataset.removedMessage)
246+ // Click handler for the filter button. Bound directly to the button
247+ // (not event delegation) so it works regardless of any capture-phase
248+ // listeners the widget or other code may have installed.
249+ function handleFilterClick(e) {
250+ if (e) e.preventDefault()
251+ const btn = document.getElementById('favorites-filter-btn')
252+ if (!btn) {
253+ try {
254+ console.warn('[programa] filter button not in DOM')
255+ } catch (e) {}
277256 return
278257 }
258+ const wasPressed = btn.getAttribute('aria-pressed') === 'true'
259+ const nowPressed = !wasPressed
260+ btn.setAttribute('aria-pressed', nowPressed ? 'true' : 'false')
261+ const listView = document.getElementById('schedule-list-view')
262+ if (listView) listView.classList.toggle('filter-favorites-only', nowPressed)
263+ updateEmptyState()
264+ const count = readFavs().length
265+ const word = count === 1 ? btn.dataset.filterOnSingular : btn.dataset.filterOnPlural
266+ const message = nowPressed
267+ ? count > 0
268+ ? `${btn.dataset.filterOnPrefix}. Mostrando ${count} ${word}.`
269+ : `${btn.dataset.filterOnPrefix}.`
270+ : btn.dataset.filterOffMessage
271+ announce(message)
272+ try {
273+ console.log('[programa] filter toggled, nowPressed=', nowPressed)
274+ } catch (e) {}
275+ }
279276
280- // "Only favourites" filter toggle
281- const filterBtn = target.closest('[data-filter-favorites]')
282- if (filterBtn) {
283- e.preventDefault()
284- const wasPressed = filterBtn.getAttribute('aria-pressed') === 'true'
285- const nowPressed = !wasPressed
286- filterBtn.setAttribute('aria-pressed', nowPressed ? 'true' : 'false')
287- const listView = document.getElementById('schedule-list-view')
288- if (listView) listView.classList.toggle('filter-favorites-only', nowPressed)
289- updateEmptyState()
290- const count = readFavs().length
291- const word = count === 1 ? filterBtn.dataset.filterOnSingular : filterBtn.dataset.filterOnPlural
292- const message = nowPressed
293- ? count > 0
294- ? `${filterBtn.dataset.filterOnPrefix}. Mostrando ${count} ${word}.`
295- : `${filterBtn.dataset.filterOnPrefix}.`
296- : filterBtn.dataset.filterOffMessage
297- announce(message)
277+ function bindFilterButton() {
278+ const btn = document.getElementById('favorites-filter-btn')
279+ if (!btn) return
280+ if (btn.dataset.bound === '1') return
281+ btn.addEventListener('click', handleFilterClick)
282+ btn.dataset.bound = '1'
283+ try {
284+ console.log('[programa] filter button bound')
285+ } catch (e) {}
286+ }
287+
288+ // Per-session favourite click handler (event delegation)
289+ document.addEventListener('click', (e) => {
290+ const favBtn = e.target.closest && e.target.closest('.favorite-btn')
291+ if (!favBtn) return
292+ e.preventDefault()
293+ const code = favBtn.getAttribute('data-session-code')
294+ if (!code) return
295+ const favs = readFavs()
296+ const idx = favs.indexOf(code)
297+ let added
298+ if (idx >= 0) {
299+ favs.splice(idx, 1)
300+ added = false
301+ } else {
302+ favs.push(code)
303+ added = true
298304 }
305+ writeFavs(favs)
306+ syncFavorites()
307+ announce(added ? favBtn.dataset.addedMessage : favBtn.dataset.removedMessage)
299308 })
300309
301- // Write a message to the live region. Clears + forces a reflow so
302- // identical back-to-back messages still re-announce.
303310 function announce(message) {
304311 if (!message) return
305312 const status = document.getElementById('favorite-status')
@@ -309,16 +316,18 @@ const schedule = await getSchedule()
309316 status.textContent = message
310317 }
311318
312- // Re-sync when the user switches to the list view (widget may have
313- // mutated localStorage in the same session).
314319 document.addEventListener('pycones:view-changed', syncFavorites)
315-
316- // Re-sync on cross-tab/window changes
317320 window.addEventListener('storage', (e) => {
318321 if (e.key === FAV_KEY) syncFavorites()
319322 })
323+ document.addEventListener('astro:page-load', bindFilterButton)
324+
325+ // Try to bind now, and also on DOM ready
326+ bindFilterButton()
327+ if (document.readyState === 'loading') {
328+ document.addEventListener('DOMContentLoaded', bindFilterButton)
329+ }
320330
321- // Initial render
322331 syncFavorites()
323332 })()
324333
0 commit comments