From 7c093f2ed7e9b024d3da479f4dfa7a5e79d2c8ce Mon Sep 17 00:00:00 2001 From: Oleksandr Horbatiuk Date: Fri, 16 Jan 2026 03:35:21 +0200 Subject: [PATCH 1/7] Issue #219: Use a ready-made solution, but replace SVG icons with font icons --- themes/custom/d8_theme/d8_theme.info.yml | 1 + themes/custom/d8_theme/d8_theme.libraries.yml | 4 + themes/custom/d8_theme/js/toggler.js | 96 +++++++++++++++++++ .../d8_theme/templates/layout/page.html.twig | 42 ++++++++ 4 files changed, 143 insertions(+) create mode 100644 themes/custom/d8_theme/js/toggler.js diff --git a/themes/custom/d8_theme/d8_theme.info.yml b/themes/custom/d8_theme/d8_theme.info.yml index 1dc5b29d..ba08b4a3 100644 --- a/themes/custom/d8_theme/d8_theme.info.yml +++ b/themes/custom/d8_theme/d8_theme.info.yml @@ -15,4 +15,5 @@ regions: footer: 'Footer' libraries: + - 'd8_theme/toggler' - 'd8_theme/tooltip' diff --git a/themes/custom/d8_theme/d8_theme.libraries.yml b/themes/custom/d8_theme/d8_theme.libraries.yml index 8363a875..b047bb39 100644 --- a/themes/custom/d8_theme/d8_theme.libraries.yml +++ b/themes/custom/d8_theme/d8_theme.libraries.yml @@ -11,6 +11,10 @@ table: component: css/table.min.css: { minified: true } +toggler: + js: + js/toggler.js: {} + tooltip: js: js/tooltip.js: {} diff --git a/themes/custom/d8_theme/js/toggler.js b/themes/custom/d8_theme/js/toggler.js new file mode 100644 index 00000000..03035180 --- /dev/null +++ b/themes/custom/d8_theme/js/toggler.js @@ -0,0 +1,96 @@ +/*! + * Color mode toggler for Bootstrap's docs (https://getbootstrap.com/) + * Copyright 2011-2025 The Bootstrap Authors + * Licensed under the Creative Commons Attribution 3.0 Unported License. + * @see https://getbootstrap.com/docs/5.3/customize/color-modes/#javascript + */ + +(() => { + 'use strict' + + const getStoredTheme = () => localStorage.getItem('theme') + const setStoredTheme = theme => localStorage.setItem('theme', theme) + + const getPreferredTheme = () => { + const storedTheme = getStoredTheme() + + if (storedTheme) { + return storedTheme + } + + return window.matchMedia('(prefers-color-scheme: dark)').matches + ? 'dark' : 'light' + } + + const setTheme = theme => { + document.documentElement.setAttribute( + 'data-bs-theme', + theme === 'auto' + ? window.matchMedia('(prefers-color-scheme: dark)').matches + ? 'dark' + : 'light' + : theme + ) + } + + setTheme(getPreferredTheme()) + + const showActiveTheme = (theme, focus = false) => { + const themeSwitcher = document.querySelector('#bd-theme') + + if (!themeSwitcher) { + return + } + + const themeSwitcherText = document.querySelector('#bd-theme-text') + const activeThemeIcon = document.querySelector('.theme-icon-active') + const btnToActive = document.querySelector(`[data-bs-theme-value="${theme}"]`) + const classOfActiveBtn = btnToActive.querySelector('i').dataset.bsThemeClass + + document.querySelectorAll('[data-bs-theme-value]').forEach(element => { + element.classList.remove('active') + element.setAttribute('aria-pressed', 'false') + }) + + btnToActive.classList.add('active') + btnToActive.setAttribute('aria-pressed', 'true') + + activeThemeIcon.classList.replace( + activeThemeIcon.dataset.bsThemeClass, + classOfActiveBtn + ) + + activeThemeIcon.dataset.bsThemeClass = classOfActiveBtn + + const themeSwitcherLabel = `${themeSwitcherText.textContent} (${btnToActive.dataset.bsThemeValue})` + + themeSwitcher.setAttribute('aria-label', themeSwitcherLabel) + + if (focus) { + themeSwitcher.focus() + } + } + + window.matchMedia('(prefers-color-scheme: dark)') + .addEventListener('change', () => { + const storedTheme = getStoredTheme() + + if (storedTheme !== 'light' && storedTheme !== 'dark') { + setTheme(getPreferredTheme()) + } + }) + + window.addEventListener('DOMContentLoaded', () => { + showActiveTheme(getPreferredTheme()) + + document.querySelectorAll('[data-bs-theme-value]') + .forEach(toggle => { + toggle.addEventListener('click', () => { + const theme = toggle.getAttribute('data-bs-theme-value') + setStoredTheme(theme) + setTheme(theme) + showActiveTheme(theme, true) + }) + }) + }) +})() diff --git a/themes/custom/d8_theme/templates/layout/page.html.twig b/themes/custom/d8_theme/templates/layout/page.html.twig index 1898ccb1..c73d1a80 100644 --- a/themes/custom/d8_theme/templates/layout/page.html.twig +++ b/themes/custom/d8_theme/templates/layout/page.html.twig @@ -71,6 +71,48 @@
{{ page.navigation }} + + + {{ page.navigation_collapsible }}
From 4abef68f8b2e442d7a4e14b5380feae9edb17f6c Mon Sep 17 00:00:00 2001 From: Oleksandr Horbatiuk Date: Sun, 18 Jan 2026 15:04:44 +0200 Subject: [PATCH 2/7] Issue #219: Replace the class that defines the background color of the navigation panel with one that adapts to dark mode, and increase the length of the field value for additional classes of this panel --- .../config/install/d8_theme.settings.yml | 2 +- .../config/install/d8_theme.settings.yml | 2 +- .../custom/d8_theme/src/Hook/D8ThemeHooks.php | 25 +++++++++++++++++++ 3 files changed, 27 insertions(+), 2 deletions(-) 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 819a0012..a690bde0 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 819a0012..a690bde0 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 4c4b968b..63ea4320 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(). */ From 441c4f2b9ff3ac16b0a4e85ff734e355de4d2b3a Mon Sep 17 00:00:00 2001 From: Oleksandr Horbatiuk Date: Sun, 18 Jan 2026 17:09:12 +0200 Subject: [PATCH 3/7] Issue #219: Use a module that already contains pre-added functionality --- README.md | 1 + composer.json | 14 +++ d8.info.yml | 1 + d8.install | 7 ++ themes/custom/d8_theme/d8_theme.info.yml | 1 - themes/custom/d8_theme/d8_theme.libraries.yml | 4 - themes/custom/d8_theme/js/toggler.js | 96 ------------------- .../d8_theme/templates/layout/page.html.twig | 42 -------- 8 files changed, 23 insertions(+), 143 deletions(-) delete mode 100644 themes/custom/d8_theme/js/toggler.js diff --git a/README.md b/README.md index 8e90774f..d53ad9c2 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 5c6f5d5a..c6bb996f 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", @@ -59,6 +60,19 @@ { "type": "composer", "url": "https://packages.drupal.org/8" + }, + { + "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": "refs/heads/project-update-bot-only" + } + } } ] } diff --git a/d8.info.yml b/d8.info.yml index 0889c9b9..e0a1b85c 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 dea4711a..daaee0c4 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/themes/custom/d8_theme/d8_theme.info.yml b/themes/custom/d8_theme/d8_theme.info.yml index ba08b4a3..1dc5b29d 100644 --- a/themes/custom/d8_theme/d8_theme.info.yml +++ b/themes/custom/d8_theme/d8_theme.info.yml @@ -15,5 +15,4 @@ regions: footer: 'Footer' libraries: - - 'd8_theme/toggler' - 'd8_theme/tooltip' diff --git a/themes/custom/d8_theme/d8_theme.libraries.yml b/themes/custom/d8_theme/d8_theme.libraries.yml index b047bb39..8363a875 100644 --- a/themes/custom/d8_theme/d8_theme.libraries.yml +++ b/themes/custom/d8_theme/d8_theme.libraries.yml @@ -11,10 +11,6 @@ table: component: css/table.min.css: { minified: true } -toggler: - js: - js/toggler.js: {} - tooltip: js: js/tooltip.js: {} diff --git a/themes/custom/d8_theme/js/toggler.js b/themes/custom/d8_theme/js/toggler.js deleted file mode 100644 index 03035180..00000000 --- a/themes/custom/d8_theme/js/toggler.js +++ /dev/null @@ -1,96 +0,0 @@ -/*! - * Color mode toggler for Bootstrap's docs (https://getbootstrap.com/) - * Copyright 2011-2025 The Bootstrap Authors - * Licensed under the Creative Commons Attribution 3.0 Unported License. - * @see https://getbootstrap.com/docs/5.3/customize/color-modes/#javascript - */ - -(() => { - 'use strict' - - const getStoredTheme = () => localStorage.getItem('theme') - const setStoredTheme = theme => localStorage.setItem('theme', theme) - - const getPreferredTheme = () => { - const storedTheme = getStoredTheme() - - if (storedTheme) { - return storedTheme - } - - return window.matchMedia('(prefers-color-scheme: dark)').matches - ? 'dark' : 'light' - } - - const setTheme = theme => { - document.documentElement.setAttribute( - 'data-bs-theme', - theme === 'auto' - ? window.matchMedia('(prefers-color-scheme: dark)').matches - ? 'dark' - : 'light' - : theme - ) - } - - setTheme(getPreferredTheme()) - - const showActiveTheme = (theme, focus = false) => { - const themeSwitcher = document.querySelector('#bd-theme') - - if (!themeSwitcher) { - return - } - - const themeSwitcherText = document.querySelector('#bd-theme-text') - const activeThemeIcon = document.querySelector('.theme-icon-active') - const btnToActive = document.querySelector(`[data-bs-theme-value="${theme}"]`) - const classOfActiveBtn = btnToActive.querySelector('i').dataset.bsThemeClass - - document.querySelectorAll('[data-bs-theme-value]').forEach(element => { - element.classList.remove('active') - element.setAttribute('aria-pressed', 'false') - }) - - btnToActive.classList.add('active') - btnToActive.setAttribute('aria-pressed', 'true') - - activeThemeIcon.classList.replace( - activeThemeIcon.dataset.bsThemeClass, - classOfActiveBtn - ) - - activeThemeIcon.dataset.bsThemeClass = classOfActiveBtn - - const themeSwitcherLabel = `${themeSwitcherText.textContent} (${btnToActive.dataset.bsThemeValue})` - - themeSwitcher.setAttribute('aria-label', themeSwitcherLabel) - - if (focus) { - themeSwitcher.focus() - } - } - - window.matchMedia('(prefers-color-scheme: dark)') - .addEventListener('change', () => { - const storedTheme = getStoredTheme() - - if (storedTheme !== 'light' && storedTheme !== 'dark') { - setTheme(getPreferredTheme()) - } - }) - - window.addEventListener('DOMContentLoaded', () => { - showActiveTheme(getPreferredTheme()) - - document.querySelectorAll('[data-bs-theme-value]') - .forEach(toggle => { - toggle.addEventListener('click', () => { - const theme = toggle.getAttribute('data-bs-theme-value') - setStoredTheme(theme) - setTheme(theme) - showActiveTheme(theme, true) - }) - }) - }) -})() diff --git a/themes/custom/d8_theme/templates/layout/page.html.twig b/themes/custom/d8_theme/templates/layout/page.html.twig index c73d1a80..1898ccb1 100644 --- a/themes/custom/d8_theme/templates/layout/page.html.twig +++ b/themes/custom/d8_theme/templates/layout/page.html.twig @@ -71,48 +71,6 @@
{{ page.navigation }} - - - {{ page.navigation_collapsible }}
From aefe72b98400bf0987827a8ce8f4d01ee2a65bf8 Mon Sep 17 00:00:00 2001 From: Oleksandr Horbatiuk Date: Sun, 18 Jan 2026 17:20:12 +0200 Subject: [PATCH 4/7] Issue #219: Update the module repository link to use the commit hash instead --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index c6bb996f..a9828610 100644 --- a/composer.json +++ b/composer.json @@ -70,7 +70,7 @@ "source": { "url": "https://git.drupalcode.org/issue/bootstrap_theme_toggler-3428288.git", "type": "git", - "reference": "refs/heads/project-update-bot-only" + "reference": "ac617b8946b72d0d85d44d4c19540ccbee03da2f" } } } From 4caddc06a4c15ee5803a6595273fb45bed35a056 Mon Sep 17 00:00:00 2001 From: Oleksandr Horbatiuk Date: Wed, 21 Jan 2026 23:28:56 +0200 Subject: [PATCH 5/7] Issue #219: Implement color switching for captcha --- .../{recaptcha.invisible.js => recaptcha.js} | 0 .../d8_captcha/js/recaptcha_preloader.js | 42 +++++++++++++++++++ .../d8_captcha/src/Hook/D8CaptchaHooks.php | 41 +++++++++++++++++- 3 files changed, 81 insertions(+), 2 deletions(-) rename modules/features/d8_captcha/js/{recaptcha.invisible.js => recaptcha.js} (100%) create mode 100644 modules/features/d8_captcha/js/recaptcha_preloader.js 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 00000000..fda028d1 --- /dev/null +++ b/modules/features/d8_captcha/js/recaptcha_preloader.js @@ -0,0 +1,42 @@ +((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 container = el.cloneNode(false); + + el.parentNode.replaceChild(container, el); + + window.grecaptcha.render(container, { + sitekey: key, + theme: query.matches ? 'dark' : 'light', + }); + + 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 a09fd9e6..ac61b4c4 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, + ); + } + } From 9f101ebe69369366bdf7978d464100201ccb4fb3 Mon Sep 17 00:00:00 2001 From: Oleksandr Horbatiuk Date: Thu, 29 Jan 2026 01:47:13 +0200 Subject: [PATCH 6/7] Issue #219: Mark repositories as non-canonical --- composer.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/composer.json b/composer.json index a9828610..f509d91d 100644 --- a/composer.json +++ b/composer.json @@ -58,10 +58,12 @@ }, "repositories": [ { + "canonical": false, "type": "composer", "url": "https://packages.drupal.org/8" }, { + "canonical": false, "type": "package", "package": { "name": "drupal/bootstrap_theme_toggler", From 56631ba3fbc90685f7bba12f0a9c6815b8047199 Mon Sep 17 00:00:00 2001 From: Oleksandr Horbatiuk Date: Thu, 29 Jan 2026 03:15:08 +0200 Subject: [PATCH 7/7] Issue #219: Switch preloader color --- modules/features/d8_captcha/js/recaptcha_preloader.js | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/modules/features/d8_captcha/js/recaptcha_preloader.js b/modules/features/d8_captcha/js/recaptcha_preloader.js index fda028d1..09905f6a 100644 --- a/modules/features/d8_captcha/js/recaptcha_preloader.js +++ b/modules/features/d8_captcha/js/recaptcha_preloader.js @@ -7,13 +7,22 @@ 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: query.matches ? 'dark' : 'light', + theme: themes[query.matches], }); const observer = new MutationObserver(() => {