diff --git a/libraries/src/MVC/View/FormView.php b/libraries/src/MVC/View/FormView.php index 6591073732f40..234fecd2e7851 100644 --- a/libraries/src/MVC/View/FormView.php +++ b/libraries/src/MVC/View/FormView.php @@ -334,8 +334,12 @@ function (Toolbar $childBar) use ($checkedOut, $itemEditable, $canDo, $user, $vi if ($formConfig && (string) $formConfig['button'] === 'show') { $targetClass = (string) $formConfig['targetclass'] ?: 'hide-aware-inline-help'; + $storage = (string) $formConfig['storage']; + $button = $toolbar->inlinehelp($targetClass); - $toolbar->inlinehelp($targetClass); + if ($storage !== '') { + $button->storage($storage); + } } } diff --git a/libraries/src/Toolbar/Button/InlinehelpButton.php b/libraries/src/Toolbar/Button/InlinehelpButton.php index 13bca5c6680a0..1ec57462d5b5a 100644 --- a/libraries/src/Toolbar/Button/InlinehelpButton.php +++ b/libraries/src/Toolbar/Button/InlinehelpButton.php @@ -18,6 +18,8 @@ * * @method self targetclass(string $value) * @method string getTargetclass() + * @method self storage(string $value) + * @method string getStorage() * * @since 4.1.0 */ @@ -53,6 +55,10 @@ protected function prepareOptions(array &$options) ] ); + if (!empty($options['storage'])) { + $options['attributes']['data-storage'] = $options['storage']; + } + parent::prepareOptions($options); } @@ -101,6 +107,7 @@ protected static function getAccessors(): array parent::getAccessors(), [ 'targetclass', + 'storage', ] ); } diff --git a/media_source/system/js/inlinehelp.es6.js b/media_source/system/js/inlinehelp.es6.js index 4579a427baa07..5a62fa67272fb 100644 --- a/media_source/system/js/inlinehelp.es6.js +++ b/media_source/system/js/inlinehelp.es6.js @@ -35,6 +35,66 @@ Joomla.toggleInlineHelp = (toggleClass) => { }); }; +/** + * The localStorage key remembering the inline help state of one page. + * + * @param {String} toggleClass The class name of the DIVs the toggler controls + * + * @return {String} + */ +const inlineHelpStorageKey = (toggleClass) => `joomla.inlinehelp.${toggleClass}.${window.location.pathname}${window.location.search}`; + +/** + * Whether the inline help was left visible on this page. + * + * @param {String} key The storage key + * + * @return {Boolean} + */ +const inlineHelpStored = (key) => { + try { + return window.localStorage.getItem(key) === '1'; + } catch (error) { + return false; + } +}; + +/** + * Remembers the inline help state of this page. Only the visible state is stored, as hidden is the default. + * + * @param {String} key The storage key + * @param {Boolean} visible Whether the inline help is visible + */ +const inlineHelpStore = (key, visible) => { + try { + if (visible) { + window.localStorage.setItem(key, '1'); + } else { + window.localStorage.removeItem(key); + } + } catch (error) { + // Storage unavailable (private mode, blocked site data): keep the default behaviour. + } +}; + +// The classes whose remembered state was already restored, so that two togglers do not cancel each other out. +const inlineHelpRestored = new Set(); + +/** + * Shows the inline help state on every toggler of a class: `inlinehelp-active` and `aria-pressed` follow the visibility. + * + * @param {String} toggleClass The class name of the DIVs the togglers control + * @param {Boolean} visible Whether the inline help is visible + */ +const inlineHelpPressed = (toggleClass, visible) => { + document.querySelectorAll('.button-inlinehelp').forEach((elToggler) => { + if ((elToggler.dataset.class ?? 'hide-aware-inline-help') === toggleClass) { + elToggler.classList.toggle('inlinehelp-active', visible); + elToggler.setAttribute('aria-pressed', visible ? 'true' : 'false'); + } + }); +}; + // Initialisation. Clicking on anything with the button-inlinehelp class will toggle the inline help. document.querySelectorAll('.button-inlinehelp').forEach((elToggler) => { // The class of the DIVs to toggle visibility on is defined by the data-class attribute of the click target. @@ -47,9 +107,29 @@ document.querySelectorAll('.button-inlinehelp').forEach((elToggler) => { return; } + // The state is remembered only when the form asks for it with storage="local". + const storageKey = elToggler.dataset.storage === 'local' ? inlineHelpStorageKey(toggleClass) : null; + + if (storageKey && !inlineHelpRestored.has(toggleClass)) { + inlineHelpRestored.add(toggleClass); + + if (inlineHelpStored(storageKey)) { + Joomla.toggleInlineHelp(toggleClass); + } + + inlineHelpPressed(toggleClass, !collection[0].classList.contains('d-none')); + } + // Add the click handler. elToggler.addEventListener('click', (event) => { event.preventDefault(); Joomla.toggleInlineHelp(toggleClass); + + if (storageKey) { + const visible = !collection[0].classList.contains('d-none'); + + inlineHelpStore(storageKey, visible); + inlineHelpPressed(toggleClass, visible); + } }); }); diff --git a/media_source/templates/administrator/atum/scss/blocks/_toolbar.scss b/media_source/templates/administrator/atum/scss/blocks/_toolbar.scss index cbaac9123cf91..727326b6e1e68 100644 --- a/media_source/templates/administrator/atum/scss/blocks/_toolbar.scss +++ b/media_source/templates/administrator/atum/scss/blocks/_toolbar.scss @@ -59,7 +59,8 @@ &:not([disabled]):hover, &:not([disabled]):active, - &:not([disabled]):focus { + &:not([disabled]):focus, + &.inlinehelp-active { color: var(--atum-btn-hvr); background-color: var(--subhead-btn-bg-hvr); border: var(--subhead-btn-border); @@ -69,6 +70,16 @@ } } + // A toggle switched off keeps focus after the click; without this it would still look pressed. + &[data-storage]:not(.inlinehelp-active):focus:not(:hover) { + color: var(--atum-btn); + background-color: var(--subhead-btn-bg); + + > span { + color: var(--subhead-btn-icon); + } + } + &.btn-success { --subhead-btn-icon: var(--atum-btn-success-icon); --subhead-btn-icon-hover: var(--atum-btn-success-icon-hvr);