diff --git a/README.md b/README.md index 8e90774ff..d53ad9c2b 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,7 @@ This installation profile requires the following extensions: - [Admin Toolbar](https://www.drupal.org/project/admin_toolbar) - [Automatic IP ban (Autoban)](https://www.drupal.org/project/autoban) - [Bootstrap](https://www.drupal.org/project/bootstrap) +- [Bootstrap Light-Dark-Color Theme Mode Toggler](https://www.drupal.org/project/bootstrap_theme_toggler) - [CAPTCHA](https://www.drupal.org/project/captcha) - [Config Export to PHP array](https://www.drupal.org/project/config2php) - [Configuration Update Manager](https://www.drupal.org/project/config_update) diff --git a/composer.json b/composer.json index 5c6f5d5a6..f509d91da 100644 --- a/composer.json +++ b/composer.json @@ -9,6 +9,7 @@ "drupal/admin_toolbar": "3.6.2", "drupal/autoban": "1.11", "drupal/bootstrap": ">=5.0.0", + "drupal/bootstrap_theme_toggler": "dev-project-update-bot-only", "drupal/captcha": "2.0.10", "drupal/config_update": "2.0.0-alpha4", "drupal/core": "11.3.1", @@ -57,8 +58,23 @@ }, "repositories": [ { + "canonical": false, "type": "composer", "url": "https://packages.drupal.org/8" + }, + { + "canonical": false, + "type": "package", + "package": { + "name": "drupal/bootstrap_theme_toggler", + "version": "dev-project-update-bot-only", + "type": "drupal-module", + "source": { + "url": "https://git.drupalcode.org/issue/bootstrap_theme_toggler-3428288.git", + "type": "git", + "reference": "ac617b8946b72d0d85d44d4c19540ccbee03da2f" + } + } } ] } diff --git a/d8.info.yml b/d8.info.yml index 0889c9b9b..e0a1b85c0 100644 --- a/d8.info.yml +++ b/d8.info.yml @@ -29,6 +29,7 @@ install: - admin_toolbar:admin_toolbar_search - admin_toolbar:admin_toolbar_tools + - bootstrap_theme_toggler:bootstrap_theme_toggler - features:features_ui - idle:idle (>=1.0.2) - module_filter:module_filter diff --git a/d8.install b/d8.install index dea4711a6..daaee0c42 100644 --- a/d8.install +++ b/d8.install @@ -45,6 +45,13 @@ function d8_update_last_removed(): int { return 11201; } +/** + * Install Bootstrap Light-Dark-Color Theme Mode Toggler module. + */ +function d8_update_11301(array &$sandbox): void { + \Drupal::classResolver(D8Setup::class)->module('bootstrap_theme_toggler'); +} + /** * Process list of update hooks. * diff --git a/modules/features/d8_captcha/js/recaptcha.invisible.js b/modules/features/d8_captcha/js/recaptcha.js similarity index 100% rename from modules/features/d8_captcha/js/recaptcha.invisible.js rename to modules/features/d8_captcha/js/recaptcha.js diff --git a/modules/features/d8_captcha/js/recaptcha_preloader.js b/modules/features/d8_captcha/js/recaptcha_preloader.js new file mode 100644 index 000000000..09905f6ae --- /dev/null +++ b/modules/features/d8_captcha/js/recaptcha_preloader.js @@ -0,0 +1,51 @@ +((window, document, $) => { + + 'use strict'; + + const query = window.matchMedia('(prefers-color-scheme: dark)'); + + window.onLoadReCaptcha = () => { + document.querySelectorAll('.g-recaptcha').forEach(el => { + const key = el.dataset.sitekey; + const themes = {true: 'dark', false: 'light'}; + const dark = el.dataset.theme === themes[true]; + const container = el.cloneNode(false); + + if (query.matches !== dark) { + const preloader = el.nextElementSibling.classList; + + preloader.remove(el.dataset.theme); + preloader.add(themes[!dark]); + } + + el.parentNode.replaceChild(container, el); + + window.grecaptcha.render(container, { + sitekey: key, + theme: themes[query.matches], + }); + + const observer = new MutationObserver(() => { + if (container.querySelector('iframe')) { + observer.disconnect(); + + $(container) + .closest('.g-recaptcha-wrapper') + .removeClass('loading') + .closest('form') + .find('.blocked-by-recaptcha') + .removeAttr('disabled') + .removeClass('blocked-by-recaptcha is-disabled'); + } + }); + + observer.observe(container, { + childList: true, + subtree: true, + }); + }); + } + + query.addEventListener('change', window.onLoadReCaptcha); + +})(window, document, jQuery); diff --git a/modules/features/d8_captcha/src/Hook/D8CaptchaHooks.php b/modules/features/d8_captcha/src/Hook/D8CaptchaHooks.php index a09fd9e62..ac61b4c40 100644 --- a/modules/features/d8_captcha/src/Hook/D8CaptchaHooks.php +++ b/modules/features/d8_captcha/src/Hook/D8CaptchaHooks.php @@ -14,6 +14,16 @@ */ final class D8CaptchaHooks extends D8HooksBase { + /** + * The URL prefix for the Google reCAPTCHA API script. + */ + private const string PREFIX = 'https://www.google.com/recaptcha/api.js?'; + + /** + * The triggered function name for the Google reCAPTCHA API script. + */ + private const string SUFFIX = 'onload=onLoadReCaptcha'; + /** * D8CaptchaHooks constructor. * @@ -62,10 +72,37 @@ public function libraryInfoAlter(array &$libraries, string $extension): void { $extension === 'recaptcha' && isset($libraries[$name = "$extension.invisible"]) ) { - $path = $this->extensionPathResolver->getPath('module', 'd8_captcha'); + $libraries[$name]['js'] = [$this->path($extension) => []]; + } + elseif ($extension === 'recaptcha_preloader') { + $items = &$libraries['connector']['js']; + + $replacements = [ + 'js/base.js' => $this->path($extension), + self::PREFIX . self::SUFFIX + => self::PREFIX . 'render=explicit&' . self::SUFFIX, + ]; - $libraries[$name]['js'] = ["/$path/js/$name.js" => []]; + foreach ($replacements as $old => $new) { + $items[$new] = $items[$old]; + + unset($items[$old]); + } } } + /** + * Generates a JavaScript file path for the specified file name. + * + * @param string $name + * The name of the JavaScript file (without extension). + */ + private function path(string $name): string { + return sprintf( + '/%s/js/%s.js', + $this->extensionPathResolver->getPath('module', 'd8_captcha'), + $name, + ); + } + } diff --git a/modules/features/d8_theming/config/install/d8_theme.settings.yml b/modules/features/d8_theming/config/install/d8_theme.settings.yml index 819a00126..a690bde0a 100644 --- a/modules/features/d8_theming/config/install/d8_theme.settings.yml +++ b/modules/features/d8_theming/config/install/d8_theme.settings.yml @@ -59,7 +59,7 @@ bootstrap_navbar_top_color: '' bootstrap_navbar_top_background: '' bootstrap_navbar_position: '' bootstrap_navbar_color: '' -bootstrap_navbar_background: bg-light +bootstrap_navbar_background: bg-body-tertiary bootstrap_navbar_top_class: '' bootstrap_navbar_class: 'order-last order-sm-first mt-auto mt-sm-0' bootstrap_navbar_offcanvas: '' diff --git a/themes/custom/d8_theme/config/install/d8_theme.settings.yml b/themes/custom/d8_theme/config/install/d8_theme.settings.yml index 819a00126..a690bde0a 100644 --- a/themes/custom/d8_theme/config/install/d8_theme.settings.yml +++ b/themes/custom/d8_theme/config/install/d8_theme.settings.yml @@ -59,7 +59,7 @@ bootstrap_navbar_top_color: '' bootstrap_navbar_top_background: '' bootstrap_navbar_position: '' bootstrap_navbar_color: '' -bootstrap_navbar_background: bg-light +bootstrap_navbar_background: bg-body-tertiary bootstrap_navbar_top_class: '' bootstrap_navbar_class: 'order-last order-sm-first mt-auto mt-sm-0' bootstrap_navbar_offcanvas: '' diff --git a/themes/custom/d8_theme/src/Hook/D8ThemeHooks.php b/themes/custom/d8_theme/src/Hook/D8ThemeHooks.php index 4c4b968bc..63ea43205 100644 --- a/themes/custom/d8_theme/src/Hook/D8ThemeHooks.php +++ b/themes/custom/d8_theme/src/Hook/D8ThemeHooks.php @@ -10,6 +10,31 @@ */ final class D8ThemeHooks { + /** + * Implements hook_form_FORM_ID_alter(). + */ + #[Hook('form_system_theme_settings_alter')] + public function formSystemThemeSettingsAlter( + array &$form, + FormStateInterface $form_state, + ?string $form_id = NULL, + ): void { + $group = &$form['components']['navbar']; + + $old = &$group['bootstrap_navbar_background']['#options']; + $new = []; + + foreach ($old as $class => $label) { + $new[$class === 'bg-light' ? 'bg-body-tertiary' : $class] = $label; + } + + $old = $new; + + foreach (['maxlength', 'size'] as $key) { + $group['bootstrap_navbar_class']["#$key"] = 50; + } + } + /** * Implements hook_form_FORM_ID_alter(). */