diff --git a/site/app/Application/Theme.php b/site/app/Application/Theme.php deleted file mode 100644 index 8ad288945..000000000 --- a/site/app/Application/Theme.php +++ /dev/null @@ -1,53 +0,0 @@ -setCookie(self::DARK); - } - - - public function setLightMode(): void - { - $this->setCookie(self::LIGHT); - } - - - public function isDarkMode(): ?bool - { - $cookie = $this->cookies->getString(CookieName::Theme); - return $cookie === self::DARK ? true : ($cookie === self::LIGHT ? false : null); - } - - - private function setCookie(string $mode): void - { - $this->cookies->set(CookieName::Theme, $mode, $this->getCookieLifetime(), sameSite: 'None'); - } - - - public function getCookieLifetime(): string - { - return '365 days'; - } - -} diff --git a/site/app/Application/Theme/Theme.php b/site/app/Application/Theme/Theme.php new file mode 100644 index 000000000..ad4217e89 --- /dev/null +++ b/site/app/Application/Theme/Theme.php @@ -0,0 +1,57 @@ +setCookie(ThemeMode::Dark); + } + + + public function setLightMode(): void + { + $this->setCookie(ThemeMode::Light); + } + + + public function isDarkMode(): ?bool + { + $cookie = $this->cookies->getString(CookieName::Theme) ?? ''; + if ($cookie === 'bright') { + // Support legacy cookie value, can be removed in August 2025 because then all legacy cookies will expire + $cookie = ThemeMode::Light->value; + } + try { + return ThemeMode::from($cookie) === ThemeMode::Dark; + } catch (ValueError) { + return null; + } + } + + + private function setCookie(ThemeMode $mode): void + { + $this->cookies->set(CookieName::Theme, $mode->value, $this->getCookieLifetime(), sameSite: 'None'); + } + + + public function getCookieLifetime(): string + { + return '365 days'; + } + +} diff --git a/site/app/Application/Theme/ThemeMode.php b/site/app/Application/Theme/ThemeMode.php new file mode 100644 index 000000000..b45095692 --- /dev/null +++ b/site/app/Application/Theme/ThemeMode.php @@ -0,0 +1,12 @@ +factory->create(); + $form->addSubmit(ThemeMode::Light->value)->onClick[] = function () use ($onSuccess): void { + $this->theme->setLightMode(); + $onSuccess(); + }; + $form->addSubmit(ThemeMode::Dark->value)->onClick[] = function () use ($onSuccess): void { + $this->theme->setDarkMode(); + $onSuccess(); + }; + return $form; + } + +} diff --git a/site/app/Http/Cookies/CookieDescriptions.php b/site/app/Http/Cookies/CookieDescriptions.php index f3f9572b2..54d4c7f67 100644 --- a/site/app/Http/Cookies/CookieDescriptions.php +++ b/site/app/Http/Cookies/CookieDescriptions.php @@ -3,7 +3,7 @@ namespace MichalSpacekCz\Http\Cookies; -use MichalSpacekCz\Application\Theme; +use MichalSpacekCz\Application\Theme\Theme; use MichalSpacekCz\DateTime\DateTimeParser; use MichalSpacekCz\Formatter\TexyFormatter; use MichalSpacekCz\ShouldNotHappenException; diff --git a/site/app/Templating/TemplateFactory.php b/site/app/Templating/TemplateFactory.php index 039d23b29..88cb683c1 100644 --- a/site/app/Templating/TemplateFactory.php +++ b/site/app/Templating/TemplateFactory.php @@ -4,7 +4,7 @@ namespace MichalSpacekCz\Templating; use Contributte\Translation\Translator; -use MichalSpacekCz\Application\Theme; +use MichalSpacekCz\Application\Theme\Theme; use MichalSpacekCz\Templating\Exceptions\WrongTemplateClassException; use Nette\Application\UI\Control; use Nette\Application\UI\TemplateFactory as UiTemplateFactory; diff --git a/site/app/Www/Presenters/BasePresenter.php b/site/app/Www/Presenters/BasePresenter.php index c2e525ae1..5a97a13a0 100644 --- a/site/app/Www/Presenters/BasePresenter.php +++ b/site/app/Www/Presenters/BasePresenter.php @@ -6,9 +6,10 @@ use DateTimeInterface; use MichalSpacekCz\Application\Locale\LocaleLink; use MichalSpacekCz\Application\Locale\LocaleLinkGenerator; -use MichalSpacekCz\Application\Theme; use MichalSpacekCz\Css\CriticalCss; use MichalSpacekCz\Css\CriticalCssFactory; +use MichalSpacekCz\Form\ThemeFormFactory; +use MichalSpacekCz\Form\UiForm; use MichalSpacekCz\User\Manager; use Nette\Application\Request; use Nette\Application\UI\InvalidLinkException; @@ -27,7 +28,7 @@ abstract class BasePresenter extends Presenter private LocaleLinkGenerator $localeLinkGenerator; - private Theme $theme; + private ThemeFormFactory $themeFormFactory; private IResponse $httpResponse; @@ -55,9 +56,9 @@ public function injectLocaleLinkGenerator(LocaleLinkGenerator $localeLinkGenerat /** * @internal */ - public function injectTheme(Theme $theme): void + public function injectThemeFormFactory(ThemeFormFactory $themeFormFactory): void { - $this->theme = $theme; + $this->themeFormFactory = $themeFormFactory; } @@ -147,22 +148,6 @@ protected function getLocaleLinkParams(): array } - public function handleDarkFuture(): never - { - $this->theme->setDarkMode(); - $this->httpResponse->setExpiration(null); - $this->redirectPermanent('this'); - } - - - public function handleBrightFuture(): never - { - $this->theme->setLightMode(); - $this->httpResponse->setExpiration(null); - $this->redirectPermanent('this'); - } - - #[Override] public function lastModified(string|int|DateTimeInterface|null $lastModified, string $etag = null, string $expire = null): void { @@ -179,4 +164,13 @@ protected function createComponentCriticalCss(): CriticalCss return $this->criticalCssFactory->create(); } + + protected function createComponentTheme(): UiForm + { + return $this->themeFormFactory->create(function (): void { + $this->httpResponse->setExpiration(null); + $this->redirect('this'); + }); + } + } diff --git a/site/app/Www/Presenters/templates/@layout.latte b/site/app/Www/Presenters/templates/@layout.latte index 2f1c163da..05f89a461 100644 --- a/site/app/Www/Presenters/templates/@layout.latte +++ b/site/app/Www/Presenters/templates/@layout.latte @@ -88,14 +88,16 @@ {ifset #footerScripts}{include #footerScripts}{/ifset} diff --git a/site/config/services.neon b/site/config/services.neon index 001cea03c..b161ffb50 100644 --- a/site/config/services.neon +++ b/site/config/services.neon @@ -14,7 +14,7 @@ services: - MichalSpacekCz\Application\Routing\RouterFactory(supportedLocales: %locales.supported%, rootDomainMapping: %locales.rootDomainMapping%, translatedRoutes: %translatedRoutes.presenters%) - @MichalSpacekCz\Application\Routing\RouterFactory::createRouter - MichalSpacekCz\Application\SanitizedPhpInfo - - MichalSpacekCz\Application\Theme + - MichalSpacekCz\Application\Theme\Theme - MichalSpacekCz\Application\WebApplication(fqdn: %domain.fqdn%) - MichalSpacekCz\Application\WindowsSubsystemForLinux - MichalSpacekCz\Articles\ArticleHeaderIconsFactory @@ -58,6 +58,7 @@ services: - MichalSpacekCz\Form\SignInHoneypotFormFactory - MichalSpacekCz\Form\TalkFormFactory(videoThumbnails: @talkVideoThumbnails) - MichalSpacekCz\Form\TalkSlidesFormFactory + - MichalSpacekCz\Form\ThemeFormFactory - MichalSpacekCz\Form\TrainingApplicationAdminFormFactory - MichalSpacekCz\Form\TrainingApplicationFormFactory - MichalSpacekCz\Form\TrainingApplicationMultipleFormFactory diff --git a/site/public/www.michalspacek.com/robots.txt b/site/public/www.michalspacek.com/robots.txt index 09fecf2d2..397b5b97a 100644 --- a/site/public/www.michalspacek.com/robots.txt +++ b/site/public/www.michalspacek.com/robots.txt @@ -4,5 +4,3 @@ Disallow: /admin Disallow: /info.php Disallow: /nette.micro Disallow: /">'> -Disallow: /*?do=darkFuture -Disallow: /*?do=brightFuture diff --git a/site/public/www.michalspacek.cz/i/css/screen-dark.css b/site/public/www.michalspacek.cz/i/css/screen-dark.css index 5aae86e0a..f8d73db1b 100644 --- a/site/public/www.michalspacek.cz/i/css/screen-dark.css +++ b/site/public/www.michalspacek.cz/i/css/screen-dark.css @@ -36,9 +36,9 @@ small a, .small a { color: #C9C9C9 !important; } blockquote, .indent { border-left-color: #2B2B2B; } tr.dateLine td, li.dateLine { border-top-color: #2B2B2B; } #header-links a { background-color: rgba(255, 255, 255, 0.06); } -#header-links a.tools { background-color: rgba(0, 255, 0, 0.08); } +#header-links a.tools, #header-links button { background-color: rgba(0, 255, 0, 0.08); } #header-links a:hover { background-color: rgba(255, 255, 255, 0.12); } -#header-links a.tools:hover { background-color: rgba(0, 255, 0, 0.16); } +#header-links a.tools:hover, #header-links button:hover { background-color: rgba(0, 255, 0, 0.16); } #header-icons small a, #footer a { color: #A9A9A9; } #header-icons small a:hover, #footer a:hover { color: #00D700; } .discarded, #statuses .discarded, .lighter { opacity: 0.5; } diff --git a/site/public/www.michalspacek.cz/i/css/screen.css b/site/public/www.michalspacek.cz/i/css/screen.css index e3ce7ecf4..5da684bbb 100644 --- a/site/public/www.michalspacek.cz/i/css/screen.css +++ b/site/public/www.michalspacek.cz/i/css/screen.css @@ -177,7 +177,7 @@ button:disabled { cursor: not-allowed; } right: 0; padding: 0 10px; } -#header-links a { +#header-links a, #header-links button { display: block; float: right; background-color: rgba(0, 0, 0, 0.04); @@ -186,9 +186,14 @@ button:disabled { cursor: not-allowed; } margin-left: 10px; text-decoration: none; } -#header-links a.tools { background-color: rgba(0, 0, 255, 0.04); } +#header-links button { + border: 0; + cursor: pointer; + font: inherit; +} +#header-links a.tools, #header-links button { background-color: rgba(0, 0, 255, 0.04); } #header-links a:hover { background-color: rgba(0, 0, 0, 0.08); } -#header-links a.tools:hover { background-color: rgba(0, 0, 255, 0.08); } +#header-links a.tools:hover, #header-links button:hover { background-color: rgba(0, 0, 255, 0.08); } .print { display: none; } .single-column { padding: 0 10px 0.5em 10px; } .with-sidebar { padding-bottom: 0.5em; } @@ -324,6 +329,7 @@ form.wide textarea, textarea.wide { height: 200px; } text-align: center; } .blog form input[type=text] { width: 85px; } +.blog#header-links form { margin: inherit; } #frm-application input, #frm-application select, #frm-applicationPreliminary input { width: 170px; } #frm-application #company td { width: 220px; } #frm-application #company th.short { width: 104px; } diff --git a/site/public/www.michalspacek.cz/robots.txt b/site/public/www.michalspacek.cz/robots.txt index 09fecf2d2..397b5b97a 100644 --- a/site/public/www.michalspacek.cz/robots.txt +++ b/site/public/www.michalspacek.cz/robots.txt @@ -4,5 +4,3 @@ Disallow: /admin Disallow: /info.php Disallow: /nette.micro Disallow: /">'> -Disallow: /*?do=darkFuture -Disallow: /*?do=brightFuture diff --git a/site/tests/Application/ThemeTest.phpt b/site/tests/Application/Theme/ThemeTest.phpt similarity index 83% rename from site/tests/Application/ThemeTest.phpt rename to site/tests/Application/Theme/ThemeTest.phpt index a20881b35..f7d3d0696 100644 --- a/site/tests/Application/ThemeTest.phpt +++ b/site/tests/Application/Theme/ThemeTest.phpt @@ -1,7 +1,7 @@ theme->setLightMode(); - Assert::same('bright', $this->response->getCookie('future')[0]->getValue()); + Assert::same('light', $this->response->getCookie('future')[0]->getValue()); } @@ -60,7 +60,7 @@ class ThemeTest extends TestCase } - public function testIsDarkModeValueBright(): void + public function testIsDarkModeValueLegacy(): void { $this->request->setCookie(self::COOKIE, 'bright'); Assert::false($this->theme->isDarkMode()); @@ -70,7 +70,7 @@ class ThemeTest extends TestCase public function testIsDarkModeValueLight(): void { $this->request->setCookie(self::COOKIE, 'light'); - Assert::null($this->theme->isDarkMode()); + Assert::false($this->theme->isDarkMode()); } }