From af2cc8c2d4dd8ac645035be46c649d8728d35880 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Pasternak?= Date: Sun, 6 Sep 2026 07:03:30 +0200 Subject: [PATCH 1/3] =?UTF-8?q?fix(importer=5Fpublikacji):=20kafelki=20?= =?UTF-8?q?=C5=BAr=C3=B3de=C5=82=20w=20stylu=20kreatora=20zg=C5=82osze?= =?UTF-8?q?=C5=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Kafle wyboru źródła danych w importerze to `` stojące wewnątrz `.callout`, więc łapała je globalna reguła z `flash_messages.css`: .callout a:not(.close-button) { font-weight: bolder; … } .callout a:not(.close-button):hover { text-decoration: underline; } Efekt: pogrubiony CAŁY kafel — razem z opisem, który miał być zwykłą czcionką — i podkreślenie wszystkich linii tekstu po najechaniu myszą. Kafle kreatora „Zgłoś publikację" wyglądają dobrze wyłącznie dlatego, że są ``, więc reguła ich nie dotyczy. Neutralizuje styl linku dla `.callout a.tile-card` (specyficzność remisowa 0,2,1, rozstrzygana kolejnością importu: `wizard_forms` idzie po `common` we wszystkich motywach). Ikona dostaje kolor motywu jako jedyny akcent, a kafle na obu ekranach unoszą się delikatnie pod kursorem — z wyłączeniem przy `prefers-reduced-motion`. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01Dmq7jpU52KwQwPLp689ny6 --- .../kafelki-importera-wyglad.bugfix.rst | 6 +++ src/bpp/static/scss/_wizard_forms.scss | 47 ++++++++++++++++++- 2 files changed, 52 insertions(+), 1 deletion(-) create mode 100644 src/bpp/newsfragments/kafelki-importera-wyglad.bugfix.rst diff --git a/src/bpp/newsfragments/kafelki-importera-wyglad.bugfix.rst b/src/bpp/newsfragments/kafelki-importera-wyglad.bugfix.rst new file mode 100644 index 000000000..8099185e4 --- /dev/null +++ b/src/bpp/newsfragments/kafelki-importera-wyglad.bugfix.rst @@ -0,0 +1,6 @@ +Kafelki wyboru źródła danych w importerze publikacji wyglądają teraz tak +samo jak kafelki kreatora „Zgłoś publikację": ikona, pogrubiony tytuł i opis +zwykłą czcionką. Wcześniej globalna reguła dla linków w ramkach ``callout`` +pogrubiała cały kafelek i podkreślała po najechaniu myszą każdą linię jego +tekstu. Kafelki na obu ekranach unoszą się teraz delikatnie pod kursorem +(z poszanowaniem ustawienia „ogranicz ruch" w systemie). diff --git a/src/bpp/static/scss/_wizard_forms.scss b/src/bpp/static/scss/_wizard_forms.scss index 57154b2b6..96bab7180 100644 --- a/src/bpp/static/scss/_wizard_forms.scss +++ b/src/bpp/static/scss/_wizard_forms.scss @@ -58,13 +58,17 @@ padding: 1.5rem; cursor: pointer; text-align: center; - transition: border-color 0.2s, box-shadow 0.2s; + transition: border-color 0.2s, box-shadow 0.2s, transform 0.2s; background: #fff; &:hover { text-decoration: none; border-color: $primary-color; box-shadow: 0 2px 8px rgba(0,0,0,0.1); + // Delikatne uniesienie — ten sam sygnał „to jest klikalne", co na + // pozostałych kafelkowych ekranach BPP. Wyłączane niżej przy + // `prefers-reduced-motion`. + transform: translateY(-3px); } &:focus { @@ -89,6 +93,7 @@ display: block; font-size: 3rem; line-height: 1.2; + color: $primary-color; } .tile-title { @@ -121,6 +126,46 @@ } } +// Kafel-LINK w calloutcie: zdejmij z niego globalne style linków. +// +// Kafle importera publikacji to `` stojące wewnątrz +// `.callout`, więc łapie je globalna reguła z `flash_messages.css`: +// +// .callout a:not(.close-button) { font-weight: bolder; … } +// .callout a:not(.close-button):hover { text-decoration: underline; } +// +// Efekt uboczny: pogrubiony CAŁY kafel (razem z opisem, który ma być zwykłą +// czcionką) i podkreślenie wszystkich linii tekstu po najechaniu myszą. +// Kafle kreatora zgłoszeń tego nie mają, bo są `` — +// i to jest wygląd docelowy dla obu ekranów. +// +// Specyficzność musi wyjść REMISOWA (0,2,1) z regułą wyżej — samo +// `.tile-card` (0,1,0) by przegrało. Remis rozstrzyga kolejność w pliku +// wynikowym, a `wizard_forms` jest importowane po `common` (czyli po +// `flash_messages`) we WSZYSTKICH motywach — patrz `app-*.scss`. +.callout a.tile-card { + font-weight: normal; + color: $body-font-color; + + &:hover, + &:focus { + color: $body-font-color; + text-decoration: none; + } +} + +// Uniesienie kafla to dekoracja — przy `prefers-reduced-motion` zostaje sama +// zmiana obramowania i cienia (nadal czytelny stan hover, bez ruchu). +@media (prefers-reduced-motion: reduce) { + .tile-card { + transition: border-color 0.2s, box-shadow 0.2s; + + &:hover { + transform: none; + } + } +} + .wizard-nav { display: grid; grid-template-columns: 1fr 1fr; From 64ce021f44ef1b442c0328084b15461c120437ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Pasternak?= Date: Sun, 6 Sep 2026 07:03:45 +0200 Subject: [PATCH 2/3] =?UTF-8?q?fix(importer=5Fpublikacji):=20=E2=80=9EWste?= =?UTF-8?q?cz"=20nie=20przywraca=20nieaktualnego=20uk=C5=82adu=20kroku=201?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Po wejściu na importer, kliknięciu kafla i naciśnięciu „Wstecz" część przeglądarek pokazywała ekran sprzed przebudowy kroku 1 — radiowy wybór źródła i listę sesji pod spodem — czyli layout, którego nie ma już nawet w kodzie (usunięty w c2a63f932). Źródłem jest cache historii HTMX-a w localStorage. `hx-history="false"` na `#importer-wizard` blokuje wyłącznie ZAPIS snapshotu; `restoreHistory()` czyta cache bezwarunkowo. Wpis zapisany przed dodaniem tej flagi przeżywał więc bezterminowo — i, skoro zapisu nigdy już nie będzie, nic nie miało go nadpisać (HTMX podmienia wpis o danym URL-u dopiero przy zapisie, a LRU wyrzuca go dopiero po 10 innych stronach z HTMX-em). Nowy `history_cache.js` kasuje przy wejściu wpisy z prefiksem URL-a importera, zostawiając cudze (np. liveops). Ładowany przed htmx.min.js, prefiks wstrzykuje szablon przez `data-prefiks`. Odtworzone i zweryfikowane w przeglądarce: przed poprawką „Wstecz" wstawiał podstawiony snapshot bez pytania serwera, po poprawce wraca pełna strona z kafelkami. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01Dmq7jpU52KwQwPLp689ny6 --- .../importer-wstecz-stary-uklad.bugfix.rst | 6 + .../importer_publikacji/js/history_cache.js | 84 +++++++++++++ .../templates/importer_publikacji/index.html | 6 + .../tests/test_index_history_cache.py | 31 +++++ tests/js/importer-history-cache.test.js | 119 ++++++++++++++++++ 5 files changed, 246 insertions(+) create mode 100644 src/bpp/newsfragments/importer-wstecz-stary-uklad.bugfix.rst create mode 100644 src/importer_publikacji/static/importer_publikacji/js/history_cache.js create mode 100644 src/importer_publikacji/tests/test_index_history_cache.py create mode 100644 tests/js/importer-history-cache.test.js diff --git a/src/bpp/newsfragments/importer-wstecz-stary-uklad.bugfix.rst b/src/bpp/newsfragments/importer-wstecz-stary-uklad.bugfix.rst new file mode 100644 index 000000000..84bdbd1fc --- /dev/null +++ b/src/bpp/newsfragments/importer-wstecz-stary-uklad.bugfix.rst @@ -0,0 +1,6 @@ +Przycisk „Wstecz" w importerze publikacji nie przywraca już nieaktualnego +układu kroku 1 (wybór źródła przyciskami radio i lista sesji pod spodem). +Przeglądarki, które odwiedziły importer przed wprowadzeniem kafelków, +trzymały jego zrzut w pamięci podręcznej historii HTMX-a — a że nowa wersja +strony celowo nic już tam nie zapisuje, nieaktualny zrzut nie miał jak +zostać nadpisany. Strona kasuje go teraz przy wejściu. diff --git a/src/importer_publikacji/static/importer_publikacji/js/history_cache.js b/src/importer_publikacji/static/importer_publikacji/js/history_cache.js new file mode 100644 index 000000000..d59620daa --- /dev/null +++ b/src/importer_publikacji/static/importer_publikacji/js/history_cache.js @@ -0,0 +1,84 @@ +// Sprzątanie NIEŚWIEŻYCH snapshotów HTMX dla stron importera publikacji. +// +// Dlaczego to w ogóle istnieje +// ---------------------------- +// `index.html` ma `hx-history="false"` na `#importer-wizard`, więc HTMX nigdy +// NIE ZAPISZE snapshotu tej strony do localStorage — przy Back/Forward pyta +// serwer o pełną stronę (patrz `_is_htmx_partial` w views/helpers.py). +// +// Problem: `restoreHistory()` w HTMX czyta cache BEZ oglądania się na +// `hx-history` — ta flaga blokuje tylko ZAPIS (`saveCurrentPageToHistory`). +// Wpisy zapisane ZANIM flaga się pojawiła (układ kroku 1 sprzed kafelków: +// radiowy wybór źródła + lista sesji pod spodem) siedzą w localStorage +// przeglądarki bezterminowo i to je HTMX przywraca po naciśnięciu „Wstecz". +// Użytkownik widzi wtedy layout, którego nie ma już nawet w kodzie. +// +// Samo się to nie naprawi: skoro zapisu nigdy nie będzie, nic tych wpisów nie +// nadpisze (HTMX podmienia wpis o tym samym URL-u dopiero przy zapisie), a +// LRU wyrzuci je dopiero po 10 innych stronach z HTMX-em. Dlatego kasujemy je +// jawnie przy każdym wejściu na stronę importera. +(function (window) { + "use strict"; + + var KLUCZ = "htmx-history-cache"; + + // Usuń z cache HTMX-a wpisy, których URL zaczyna się od `prefiks`. + // Zwraca liczbę usuniętych wpisów (0 gdy nie było czego usuwać). + function usunWpisy(storage, prefiks) { + var surowe = storage.getItem(KLUCZ); + if (!surowe) { + return 0; + } + + var cache; + try { + cache = JSON.parse(surowe); + } catch (e) { + // Uszkodzony JSON (obcy kod pisał pod ten klucz, przerwany zapis). + // HTMX sam traktuje taki cache jak pusty (`parseJSON` zwraca null), + // więc kasujemy wpis w całości — inaczej zostawilibyśmy śmieć, + // którego i tak nikt nie odczyta. + storage.removeItem(KLUCZ); + return 0; + } + + if (!Array.isArray(cache)) { + storage.removeItem(KLUCZ); + return 0; + } + + var zostaje = cache.filter(function (wpis) { + return !( + wpis && + typeof wpis.url === "string" && + wpis.url.indexOf(prefiks) === 0 + ); + }); + + var usuniete = cache.length - zostaje.length; + if (usuniete > 0) { + storage.setItem(KLUCZ, JSON.stringify(zostaje)); + } + return usuniete; + } + + window.bppImporterHistoryCache = { usunWpisy: usunWpisy, KLUCZ: KLUCZ }; + + // Auto-start: prefiks URL-a importera wstrzykuje szablon (`data-prefiks`), + // żeby nie zaszywać tu ścieżki na sztywno. + var skrypt = window.document.currentScript; + if (!skrypt) { + return; + } + var prefiks = skrypt.getAttribute("data-prefiks"); + if (!prefiks) { + return; + } + try { + usunWpisy(window.localStorage, prefiks); + } catch (e) { + // localStorage niedostępny (tryb prywatny, zablokowane dane witryn) — + // wtedy HTMX też go nie odczyta, więc nieświeży snapshot nie ma jak + // się pojawić i nie ma czego sprzątać. Strona działa normalnie. + } +})(window); diff --git a/src/importer_publikacji/templates/importer_publikacji/index.html b/src/importer_publikacji/templates/importer_publikacji/index.html index 15b73ce13..b9f84fa48 100644 --- a/src/importer_publikacji/templates/importer_publikacji/index.html +++ b/src/importer_publikacji/templates/importer_publikacji/index.html @@ -49,6 +49,12 @@ animation: wizardFadeIn 0.3s ease-out; } + {# Kasuje nieświeże snapshoty HTMX-a dla stron importera z localStorage. #} + {# Musi pójść PRZED htmx.min.js: `restoreHistory()` czyta cache mimo #} + {# `hx-history="false"` (ta flaga blokuje tylko zapis), więc bez tego #} + {# „Wstecz" przywracał układ kroku 1 sprzed kafelków. Szczegóły w pliku. #} + {# HTMX self-hostowany — bez zależności od zewnętrznego CDN-a. #}