Merge branch '5.3'
authorAlexander Ebert <ebert@woltlab.com>
Mon, 21 Dec 2020 17:29:50 +0000 (18:29 +0100)
committerAlexander Ebert <ebert@woltlab.com>
Mon, 21 Dec 2020 17:29:50 +0000 (18:29 +0100)
1  2 
wcfsetup/install/files/acp/templates/styleAdd.tpl
wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Page/Action.js
wcfsetup/install/files/lib/system/form/builder/field/CheckboxFormField.class.php

index 2af0bb30806cdfe92831b6fc1344bffbe2e8fe78,42d507b304d41ac4159dbb4cc739eb8fc12cc0fd..0b3e82e71381b0ecd7874a2d528a517a93200b9e
  /**
   * Provides page actions such as "jump to top" and clipboard actions.
   *
 - * @author    Alexander Ebert
 - * @copyright 2001-2020 WoltLab GmbH
 - * @license   GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
 - * @module    WoltLabSuite/Core/Ui/Page/Action
 + * @author  Alexander Ebert
 + * @copyright  2001-2020 WoltLab GmbH
 + * @license  GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
 + * @module  WoltLabSuite/Core/Ui/Page/Action
   */
 -define(['Dictionary', 'Language'], function (Dictionary, Language) {
 -      'use strict';
 -      
 -      var _buttons = new Dictionary();
 -      
 -      /** @var {Element} */
 -      var _container;
 -      
 -      var _didInit = false;
 -      
 -      var _lastPosition = -1;
 -      
 -      /** @var {Element} */
 -      var _toTopButton;
 -      
 -      /** @var {Element} */
 -      var _wrapper;
 -      
 -      var _resetLastPosition = window.debounce(function () {
 -              _lastPosition = -1;
 -      }, 50, false);
 -      
 -      /**
 -       * @exports     WoltLabSuite/Core/Ui/Page/Action
 -       */
 -      return {
 -              /**
 -               * Initializes the page action container.
 -               */
 -              setup: function () {
 -                      if (_didInit) {
 -                              return;
 -                      }
 -                      
 -                      _didInit = true;
 -                      
 -                      _wrapper = elCreate('div');
 -                      _wrapper.className = 'pageAction';
 -                      
 -                      _container = elCreate('div');
 -                      _container.className = 'pageActionButtons';
 -                      _wrapper.appendChild(_container);
 -                      
 -                      _toTopButton = this._buildToTopButton();
 -                      _wrapper.appendChild(_toTopButton);
 -                      
 -                      document.body.appendChild(_wrapper);
 -                      
 -                      var debounce = window.debounce(this._onScroll.bind(this), 100, false);
 -                      window.addEventListener(
 -                              "scroll",
 -                              function () {
 -                                      if (_lastPosition === -1) {
 -                                              _lastPosition = window.pageYOffset;
 -                                              
 -                                              // Invoke the scroll handler once to immediately respond to
 -                                              // the user action before debouncing all further calls.
 -                                              window.setTimeout(function () {
 -                                                      this._onScroll();
 -                                                      
 -                                                      _lastPosition = window.pageYOffset;
 -                                              }.bind(this), 60);
 -                                      }
 -
 -                                      debounce();
 -                              }.bind(this),
 -                              {passive: true}
 -                      );
 -                      
 -                      window.addEventListener("touchstart", function () {
 -                              // Force a reset of the scroll position to trigger an immediate reaction
 -                              // when the user touches the display again.
 -                              if (_lastPosition !== -1) {
 -                                      _lastPosition = -1;
 -                              }
 -                      }, {passive: true});
 -                      
 -                      this._onScroll();
 -              },
 -              
 -              _buildToTopButton: function () {
 -                      var button = elCreate('a');
 -                      button.className = 'button buttonPrimary pageActionButtonToTop initiallyHidden jsTooltip';
 -                      button.href = '';
 -                      elAttr(button, 'title', Language.get('wcf.global.scrollUp'));
 -                      elAttr(button, 'aria-hidden', 'true');
 -                      button.innerHTML = '<span class="icon icon32 fa-angle-up"></span>';
 -                      
 -                      button.addEventListener(WCF_CLICK_EVENT, this._scrollTopTop.bind(this));
 -                      
 -                      return button;
 -              },
 -              
 -              _onScroll: function () {
 -                      if (document.documentElement.classList.contains('disableScrolling')) {
 -                              // Ignore any scroll events that take place while body scrolling is disabled,
 -                              // because it messes up the scroll offsets.
 -                              return;
 -                      }
 -                      
 -                      var offset = window.pageYOffset;
 -                      if (offset === _lastPosition) {
 -                              // Ignore any scroll event that is fired but without a position change. This can
 -                              // happen after closing a dialog that prevented the body from being scrolled.
 -                              _resetLastPosition();
 -                              return;
 -                      }
 -                      
 -                      if (offset >= 300) {
 -                              if (_toTopButton.classList.contains('initiallyHidden')) {
 -                                      _toTopButton.classList.remove('initiallyHidden');
 -                              }
 -                              
 -                              elAttr(_toTopButton, 'aria-hidden', 'false');
 -                      }
 -                      else {
 -                              elAttr(_toTopButton, 'aria-hidden', 'true');
 -                      }
 -                      
 -                      this._renderContainer();
 -                      
 -                      if (_lastPosition !== -1) {
 -                              _wrapper.classList[offset < _lastPosition ? 'remove' : 'add']('scrolledDown');
 -                      }
 -                      
 -                      _lastPosition = -1;
 -              },
 -              
 -              /**
 -               * @param {Event} event
 -               */
 -              _scrollTopTop: function (event) {
 -                      event.preventDefault();
 -                      
 -                      elById('top').scrollIntoView({behavior: 'smooth'});
 -              },
 -              
 -              /**
 -               * Adds a button to the page action list. You can optionally provide a button name to
 -               * insert the button right before it. Unmatched button names or empty value will cause
 -               * the button to be prepended to the list.
 -               *
 -               * @param       {string}        buttonName              unique identifier
 -               * @param       {Element}       button                  button element, must not be wrapped in a <li>
 -               * @param       {string=}       insertBeforeButton      insert button before element identified by provided button name
 -               */
 -              add: function (buttonName, button, insertBeforeButton) {
 -                      this.setup();
 -                      
 -                      // The wrapper is required for backwards compatibility, because some implementations rely on a
 -                      // dedicated parent element to insert elements, for example, for drop-down menus.
 -                      var wrapper = elCreate('div');
 -                      wrapper.className = 'pageActionButton';
 -                      wrapper.name = buttonName;
 -                      elAttr(wrapper, 'aria-hidden', 'true');
 -                      
 -                      button.classList.add('button');
 -                      button.classList.add('buttonPrimary');
 -                      wrapper.appendChild(button);
 -                      
 -                      var insertBefore = null;
 -                      if (insertBeforeButton) {
 -                              insertBefore = _buttons.get(insertBeforeButton);
 -                              if (insertBefore !== undefined) {
 -                                      insertBefore = insertBefore.parentNode;
 -                              }
 -                      }
 -                      
 -                      if (insertBefore === null && _container.childElementCount) {
 -                              insertBefore = _container.children[0];
 -                      }
 -                      if (insertBefore === null) {
 -                              insertBefore = _container.firstChild;
 -                      }
 -                      
 -                      _container.insertBefore(wrapper, insertBefore);
 -                      _wrapper.classList.remove('scrolledDown');
 -                      
 -                      _buttons.set(buttonName, button);
 -                      
 -                      // Query a layout related property to force a reflow, otherwise the transition is optimized away.
 -                      // noinspection BadExpressionStatementJS
 -                      wrapper.offsetParent;
 -                      
 -                      // Toggle the visibility to force the transition to be applied.
 -                      elAttr(wrapper, 'aria-hidden', 'false');
 -                      
 -                      this._renderContainer();
 -              },
 -              
 -              /**
 -               * Returns true if there is a registered button with the provided name.
 -               *
 -               * @param       {string}        buttonName      unique identifier
 -               * @return      {boolean}       true if there is a registered button with this name
 -               */
 -              has: function (buttonName) {
 -                      return _buttons.has(buttonName);
 -              },
 -              
 -              /**
 -               * Returns the stored button by name or undefined.
 -               *
 -               * @param       {string}        buttonName      unique identifier
 -               * @return      {Element}       button element or undefined
 -               */
 -              get: function (buttonName) {
 -                      return _buttons.get(buttonName);
 -              },
 -              
 -              /**
 -               * Removes a button by its button name.
 -               *
 -               * @param       {string}        buttonName      unique identifier
 -               */
 -              remove: function (buttonName) {
 -                      var button = _buttons.get(buttonName);
 -                      if (button !== undefined) {
 -                              var listItem = button.parentNode;
 -                              var callback = function () {
 -                                      try {
 -                                              if (elAttrBool(listItem, 'aria-hidden')) {
 -                                                      _container.removeChild(listItem);
 -                                                      _buttons.delete(buttonName);
 -                                              }
 -                                              
 -                                              listItem.removeEventListener('transitionend', callback);
 -                                      }
 -                                      catch (e) {
 -                                              // ignore errors if the element has already been removed
 -                                      }
 -                              };
 -                              
 -                              listItem.addEventListener('transitionend', callback);
 -                              
 -                              this.hide(buttonName);
 -                      }
 -              },
 -              
 -              /**
 -               * Hides a button by its button name.
 -               *
 -               * @param       {string}        buttonName      unique identifier
 -               */
 -              hide: function (buttonName) {
 -                      var button = _buttons.get(buttonName);
 -                      if (button) {
 -                              elAttr(button.parentNode, 'aria-hidden', 'true');
 -                              this._renderContainer();
 -                      }
 -              },
 -              
 -              /**
 -               * Shows a button by its button name.
 -               *
 -               * @param       {string}        buttonName      unique identifier
 -               */
 -              show: function (buttonName) {
 -                      var button = _buttons.get(buttonName);
 -                      if (button) {
 -                              if (button.parentNode.classList.contains('initiallyHidden')) {
 -                                      button.parentNode.classList.remove('initiallyHidden');
 -                              }
 -                              
 -                              elAttr(button.parentNode, 'aria-hidden', 'false');
 -                              _wrapper.classList.remove('scrolledDown');
 -                              this._renderContainer();
 -                      }
 -              },
 -              
 -              /**
 -               * Toggles the container's visibility.
 -               *
 -               * @protected
 -               */
 -              _renderContainer: function () {
 -                      var hasVisibleItems = false;
 -                      if (_container.childElementCount) {
 -                              for (var i = 0, length = _container.childElementCount; i < length; i++) {
 -                                      if (elAttr(_container.children[i], 'aria-hidden') === 'false') {
 -                                              hasVisibleItems = true;
 -                                              break;
 -                                      }
 -                              }
 -                      }
 -                      
 -                      _container.classList[(hasVisibleItems ? 'add' : 'remove')]('active');
 -              }
 -      };
 +define(["require", "exports", "tslib", "../../Core", "../../Language"], function (require, exports, tslib_1, Core, Language) {
 +    "use strict";
 +    Object.defineProperty(exports, "__esModule", { value: true });
 +    exports.show = exports.hide = exports.remove = exports.get = exports.has = exports.add = exports.setup = void 0;
 +    Core = tslib_1.__importStar(Core);
 +    Language = tslib_1.__importStar(Language);
 +    const _buttons = new Map();
 +    let _container;
 +    let _didInit = false;
 +    let _lastPosition = -1;
 +    let _toTopButton;
 +    let _wrapper;
++    const _resetLastPosition = Core.debounce(() => {
++        _lastPosition = -1;
++    }, 50);
 +    function buildToTopButton() {
 +        const button = document.createElement("a");
 +        button.className = "button buttonPrimary pageActionButtonToTop initiallyHidden jsTooltip";
 +        button.href = "";
 +        button.title = Language.get("wcf.global.scrollUp");
 +        button.setAttribute("aria-hidden", "true");
 +        button.innerHTML = '<span class="icon icon32 fa-angle-up"></span>';
 +        button.addEventListener("click", scrollToTop);
 +        return button;
 +    }
 +    function onScroll() {
 +        if (document.documentElement.classList.contains("disableScrolling")) {
 +            // Ignore any scroll events that take place while body scrolling is disabled,
 +            // because it messes up the scroll offsets.
 +            return;
 +        }
 +        const offset = window.pageYOffset;
 +        if (offset === _lastPosition) {
 +            // Ignore any scroll event that is fired but without a position change. This can
 +            // happen after closing a dialog that prevented the body from being scrolled.
++            _resetLastPosition();
 +            return;
 +        }
 +        if (offset >= 300) {
 +            if (_toTopButton.classList.contains("initiallyHidden")) {
 +                _toTopButton.classList.remove("initiallyHidden");
 +            }
 +            _toTopButton.setAttribute("aria-hidden", "false");
 +        }
 +        else {
 +            _toTopButton.setAttribute("aria-hidden", "true");
 +        }
 +        renderContainer();
 +        if (_lastPosition !== -1) {
 +            _wrapper.classList[offset < _lastPosition ? "remove" : "add"]("scrolledDown");
 +        }
-         _lastPosition = offset;
++        _lastPosition = -1;
 +    }
 +    function scrollToTop(event) {
 +        event.preventDefault();
 +        const topAnchor = document.getElementById("top");
 +        topAnchor.scrollIntoView({ behavior: "smooth" });
 +    }
 +    /**
 +     * Toggles the container's visibility.
 +     */
 +    function renderContainer() {
 +        const visibleChild = Array.from(_container.children).find((element) => {
 +            return element.getAttribute("aria-hidden") === "false";
 +        });
 +        _container.classList[visibleChild ? "add" : "remove"]("active");
 +    }
 +    /**
 +     * Initializes the page action container.
 +     */
 +    function setup() {
 +        if (_didInit) {
 +            return;
 +        }
 +        _didInit = true;
 +        _wrapper = document.createElement("div");
 +        _wrapper.className = "pageAction";
 +        _container = document.createElement("div");
 +        _container.className = "pageActionButtons";
 +        _wrapper.appendChild(_container);
 +        _toTopButton = buildToTopButton();
 +        _wrapper.appendChild(_toTopButton);
 +        document.body.appendChild(_wrapper);
-         window.addEventListener("scroll", Core.debounce(onScroll, 100), { passive: true });
++        const debounce = Core.debounce(onScroll, 100);
++        window.addEventListener("scroll", () => {
++            if (_lastPosition === -1) {
++                _lastPosition = window.pageYOffset;
++                // Invoke the scroll handler once to immediately respond to
++                // the user action before debouncing all further calls.
++                window.setTimeout(() => {
++                    onScroll();
++                    _lastPosition = window.pageYOffset;
++                }, 60);
++            }
++            debounce();
++        }, { passive: true });
++        window.addEventListener("touchstart", () => {
++            // Force a reset of the scroll position to trigger an immediate reaction
++            // when the user touches the display again.
++            if (_lastPosition !== -1) {
++                _lastPosition = -1;
++            }
++        }, { passive: true });
 +        onScroll();
 +    }
 +    exports.setup = setup;
 +    /**
 +     * Adds a button to the page action list. You can optionally provide a button name to
 +     * insert the button right before it. Unmatched button names or empty value will cause
 +     * the button to be prepended to the list.
 +     */
 +    function add(buttonName, button, insertBeforeButton) {
 +        setup();
 +        // The wrapper is required for backwards compatibility, because some implementations rely on a
 +        // dedicated parent element to insert elements, for example, for drop-down menus.
 +        const wrapper = document.createElement("div");
 +        wrapper.className = "pageActionButton";
 +        wrapper.dataset.name = buttonName;
 +        wrapper.setAttribute("aria-hidden", "true");
 +        button.classList.add("button");
 +        button.classList.add("buttonPrimary");
 +        wrapper.appendChild(button);
 +        let insertBefore = null;
 +        if (insertBeforeButton) {
 +            insertBefore = _buttons.get(insertBeforeButton) || null;
 +            if (insertBefore) {
 +                insertBefore = insertBefore.parentElement;
 +            }
 +        }
 +        if (!insertBefore && _container.childElementCount) {
 +            insertBefore = _container.children[0];
 +        }
 +        if (!insertBefore) {
 +            insertBefore = _container.firstChild;
 +        }
 +        _container.insertBefore(wrapper, insertBefore);
 +        _wrapper.classList.remove("scrolledDown");
 +        _buttons.set(buttonName, button);
 +        // Query a layout related property to force a reflow, otherwise the transition is optimized away.
 +        // noinspection BadExpressionStatementJS
 +        wrapper.offsetParent;
 +        // Toggle the visibility to force the transition to be applied.
 +        wrapper.setAttribute("aria-hidden", "false");
 +        renderContainer();
 +    }
 +    exports.add = add;
 +    /**
 +     * Returns true if there is a registered button with the provided name.
 +     */
 +    function has(buttonName) {
 +        return _buttons.has(buttonName);
 +    }
 +    exports.has = has;
 +    /**
 +     * Returns the stored button by name or undefined.
 +     */
 +    function get(buttonName) {
 +        return _buttons.get(buttonName);
 +    }
 +    exports.get = get;
 +    /**
 +     * Removes a button by its button name.
 +     */
 +    function remove(buttonName) {
 +        const button = _buttons.get(buttonName);
 +        if (button !== undefined) {
 +            const listItem = button.parentElement;
 +            const callback = () => {
 +                try {
 +                    if (Core.stringToBool(listItem.getAttribute("aria-hidden"))) {
 +                        _container.removeChild(listItem);
 +                        _buttons.delete(buttonName);
 +                    }
 +                    listItem.removeEventListener("transitionend", callback);
 +                }
 +                catch (e) {
 +                    // ignore errors if the element has already been removed
 +                }
 +            };
 +            listItem.addEventListener("transitionend", callback);
 +            hide(buttonName);
 +        }
 +    }
 +    exports.remove = remove;
 +    /**
 +     * Hides a button by its button name.
 +     */
 +    function hide(buttonName) {
 +        const button = _buttons.get(buttonName);
 +        if (button) {
 +            const parent = button.parentElement;
 +            parent.setAttribute("aria-hidden", "true");
 +            renderContainer();
 +        }
 +    }
 +    exports.hide = hide;
 +    /**
 +     * Shows a button by its button name.
 +     */
 +    function show(buttonName) {
 +        const button = _buttons.get(buttonName);
 +        if (button) {
 +            const parent = button.parentElement;
 +            if (parent.classList.contains("initiallyHidden")) {
 +                parent.classList.remove("initiallyHidden");
 +            }
 +            parent.setAttribute("aria-hidden", "false");
 +            _wrapper.classList.remove("scrolledDown");
 +            renderContainer();
 +        }
 +    }
 +    exports.show = show;
  });
index 6e541b716b8d1e31400d044e92c4ce5961525205,495577b3f4c8771e63404cdcc55922fbd5d45524..d72b610707ce3b7cd440e3c47b1bcf71e50d71c8
@@@ -11,14 -11,7 +11,9 @@@ use wcf\system\WCF
   * @package   WoltLabSuite\Core\System\Form\Builder\Field
   * @since     5.3
   */
 -class CheckboxFormField extends BooleanFormField {
 +class CheckboxFormField extends BooleanFormField implements ICssClassFormField {
 +      use TCssClassFormField;
 +      
-       /**
-        * @inheritDoc
-        */
-       protected $templateName = '__checkboxFormField';
-       
        /**
         * @inheritDoc
         */