Overhauled to top/inline editor buttons
authorAlexander Ebert <ebert@woltlab.com>
Thu, 28 Jan 2016 16:14:00 +0000 (17:14 +0100)
committerAlexander Ebert <ebert@woltlab.com>
Thu, 28 Jan 2016 16:14:00 +0000 (17:14 +0100)
13 files changed:
com.woltlab.wcf/templates/headIncludeJavaScript.tpl
wcfsetup/install/files/js/WCF.js
wcfsetup/install/files/js/WoltLab/WCF/BootstrapFrontend.js
wcfsetup/install/files/js/WoltLab/WCF/Controller/Clipboard.js
wcfsetup/install/files/js/WoltLab/WCF/Controller/Popover.js
wcfsetup/install/files/js/WoltLab/WCF/Ui/Dropdown/Simple.js
wcfsetup/install/files/js/WoltLab/WCF/Ui/Page/Action.js [new file with mode: 0644]
wcfsetup/install/files/js/WoltLab/WCF/Ui/Page/JumpToTop.js [new file with mode: 0644]
wcfsetup/install/files/js/WoltLab/WCF/Ui/Tooltip.js
wcfsetup/install/files/style/ui/dropdown.scss
wcfsetup/install/files/style/ui/dropdownInteractive.scss
wcfsetup/install/files/style/ui/pageAction.scss [new file with mode: 0644]
wcfsetup/install/files/style/ui/popover.scss

index 80dd468a0f18484edc8f52de62281fcfe2783199..1e158c81d1911aa3a42d3b1639c732bb4056d25d 100644 (file)
@@ -75,6 +75,7 @@ requirejs.config({
                        'wcf.global.page.previous': '{capture assign=pagePrevious}{lang}wcf.global.page.previous{/lang}{/capture}{@$pagePrevious|encodeJS}',
                        'wcf.global.pageDirection': '{lang}wcf.global.pageDirection{/lang}',
                        'wcf.global.reason': '{lang}wcf.global.reason{/lang}',
+                       'wcf.global.scrollUp': '{lang}wcf.global.scrollUp{/lang}',
                        'wcf.global.sidebar.hideLeftSidebar': '{lang}wcf.global.sidebar.hideLeftSidebar{/lang}',
                        'wcf.global.sidebar.hideRightSidebar': '{lang}wcf.global.sidebar.hideRightSidebar{/lang}',
                        'wcf.global.sidebar.showLeftSidebar': '{lang}wcf.global.sidebar.showLeftSidebar{/lang}',
@@ -87,7 +88,6 @@ requirejs.config({
                        'wcf.page.sitemap': '{lang}wcf.page.sitemap{/lang}',
                        'wcf.style.changeStyle': '{lang}wcf.style.changeStyle{/lang}',
                        'wcf.user.activityPoint': '{lang}wcf.user.activityPoint{/lang}',
-                       'wcf.style.changeStyle': '{lang}wcf.style.changeStyle{/lang}',
                        'wcf.user.panel.markAllAsRead': '{lang}wcf.user.panel.markAllAsRead{/lang}',
                        'wcf.user.panel.markAsRead': '{lang}wcf.user.panel.markAsRead{/lang}',
                        'wcf.user.panel.settings': '{lang}wcf.user.panel.settings{/lang}',
index 1f2763acbf72106c7dec3706401393c835e86e79..5672aec6a1d5cac040311d82f21f299444ba7dbb 100755 (executable)
@@ -1010,6 +1010,7 @@ WCF.Dropdown.Interactive.Handler = {
                if (this._dropdownContainer === null) {
                        this._dropdownContainer = $('<div class="dropdownMenuContainer" />').appendTo(document.body);
                        WCF.CloseOverlayHandler.addCallback('WCF.Dropdown.Interactive.Handler', $.proxy(this.closeAll, this));
+                       window.addEventListener('scroll', this.closeAll.bind(this));
                }
                
                var $instance = new WCF.Dropdown.Interactive.Instance(this._dropdownContainer, triggerElement, identifier, options);
index 237669d72e51c4e254c35b9b2818866a588e8012..d8b2a6d8fba56dacc715288ccf96dbefdc112ecc 100644 (file)
@@ -8,12 +8,12 @@
  */
 define(
        [
-               'Ajax',                           'WoltLab/WCF/Bootstrap',   'WoltLab/WCF/Controller/Sitemap', 'WoltLab/WCF/Controller/Style/Changer',
-               'WoltLab/WCF/Controller/Popover'
+               'Ajax',                           'WoltLab/WCF/Bootstrap',         'WoltLab/WCF/Controller/Sitemap', 'WoltLab/WCF/Controller/Style/Changer',
+               'WoltLab/WCF/Controller/Popover', 'WoltLab/WCF/Ui/Page/JumpToTop'
        ],
        function(
-               Ajax,                              Bootstrap,                 ControllerSitemap,                ControllerStyleChanger,
-               ControllerPopover
+               Ajax,                              Bootstrap,                       ControllerSitemap,                ControllerStyleChanger,
+               ControllerPopover,                 UiPageJumpToTop
        )
 {
        "use strict";
@@ -38,6 +38,8 @@ define(
                        
                        this._initUserPopover();
                        this._invokeBackgroundQueue(options.backgroundQueue.url, options.backgroundQueue.force);
+                       
+                       new UiPageJumpToTop();
                },
                
                /**
index b9e80cb12c80be9d68378e8c2aaf99fca9621447..4f45012af0b6f4200a67f8ef85a6895928952d7b 100644 (file)
@@ -10,18 +10,21 @@ define(
        [
                'Ajax',         'Core',     'Dictionary',      'EventHandler',
                'Language',     'List',     'ObjectMap',       'Dom/ChangeListener',
-               'Dom/Traverse', 'Dom/Util', 'Ui/Confirmation', 'Ui/SimpleDropdown'
+               'Dom/Traverse', 'Dom/Util', 'Ui/Confirmation', 'Ui/SimpleDropdown',
+               'WoltLab/WCF/Ui/Page/Action'
        ],
        function(
                Ajax,            Core,       Dictionary,        EventHandler,
                Language,        List,       ObjectMap,         DomChangeListener,
-               DomTraverse,     DomUtil,    UiConfirmation,    UiSimpleDropdown
+               DomTraverse,     DomUtil,    UiConfirmation,    UiSimpleDropdown,
+               UiPageAction
        )
 {
        "use strict";
        
        var _containers = new Dictionary();
        var _editors = new Dictionary();
+       var _editorDropdowns = new Dictionary();
        var _elements = elByClass('jsClipboardContainer');
        var _itemData = new ObjectMap();
        var _knownCheckboxes = new List();
@@ -36,7 +39,7 @@ define(
         * 
         * @exports     WoltLab/WCF/Controller/Clipboard
         */
-       var ControllerClipboard = {
+       return {
                /**
                 * Initializes the clipboard API handler.
                 * 
@@ -57,14 +60,12 @@ define(
                        }
                        
                        this._initContainers();
-                       this._initEditors();
                        
                        if (_options.hasMarkedItems && _elements.length) {
                                this._loadMarkedItems();
                        }
                        
                        DomChangeListener.add('WoltLab/WCF/Controller/Clipboard', this._initContainers.bind(this));
-                       DomChangeListener.add('WoltLab/WCF/Controller/Clipboard', this._initEditors.bind(this));
                },
                
                /**
@@ -114,35 +115,6 @@ define(
                        }
                },
                
-               /**
-                * Initializes the clipboard editor dropdowns.
-                */
-               _initEditors: function() {
-                       var getTypes = function(editor) {
-                               try {
-                                       var types = elData(editor, 'types');
-                                       if (typeof types === 'string') {
-                                               return JSON.parse('{ "types": ' + types.replace(/'/g, '"') + '}').types;
-                                       }
-                               }
-                               catch (e) {
-                                       throw new Error("Expected a valid 'data-type' attribute for element '" + DomUtil.identify(editor) + "'.");
-                               }
-                               
-                               return [];
-                       };
-                       
-                       var editors = elByClass('jsClipboardEditor');
-                       for (var i = 0, length = editors.length; i < length; i++) {
-                               var editor = editors[i];
-                               var types = getTypes(editor);
-                               
-                               for (var j = 0, innerLength = types.length; j < innerLength; j++) {
-                                       _editors.set(types[j], editor);
-                               }
-                       }
-               },
-               
                /**
                 * Loads marked items from clipboard.
                 */
@@ -237,8 +209,8 @@ define(
                 * Saves the state for given item object ids.
                 * 
                 * @param       {string}                type            object type
-                * @param       {array<integer>}        objectIds       item object ids
-                * @param       {boolean]               isMarked        true if marked
+                * @param       {array<int>}            objectIds       item object ids
+                * @param       {boolean}               isMarked        true if marked
                 */
                _saveState: function(type, objectIds, isMarked) {
                        Ajax.api(this, {
@@ -274,10 +246,6 @@ define(
                                        listItem: listItem,
                                        responseData: null
                                });
-                               
-                               if (typeof window.jQuery === 'function') {
-                                       window.jQuery(_editors.get(type)).trigger('clipboardAction', [ type, data.actionName, data.parameters ]);
-                               }
                        };
                        
                        var confirmMessage = (typeof data.internalData.confirmMessage === 'string') ? data.internalData.confirmMessage : '';
@@ -376,10 +344,6 @@ define(
                                                listItem: listItem,
                                                responseData: responseData
                                        });
-                                       
-                                       if (typeof window.jQuery === 'function') {
-                                               window.jQuery(_editors.get(type)).trigger('clipboardActionResponse', [ responseData, type, data.actionName, data.parameters ]);
-                                       }
                                }
                                
                                this._loadMarkedItems();
@@ -434,17 +398,13 @@ define(
                                                        containerData.checkboxes[i].checked = false;
                                                }
                                                
-                                               _editors.get(data.returnValues.objectType).innerHTML = '';
+                                               UiPageAction.remove('wcfClipboard-' + data.returnValues.objectType);
                                        }
                                }).bind(this));
                                
                                return;
                        }
                        
-                       // clear editors
-                       _editors.forEach(function(editor) {
-                               editor.innerHTML = '';
-                       });
                        _itemData = new ObjectMap();
                        
                        // rebuild markings
@@ -455,52 +415,71 @@ define(
                                this._rebuildMarkings(containerData, objectIds);
                        }).bind(this));
                        
-                       // no marked items
+                       var keepEditors = [], typeName;
+                       if (data.returnValues && data.returnValues.items) {
+                               for (typeName in data.returnValues.items) {
+                                       if (data.returnValues.items.hasOwnProperty(typeName)) {
+                                               keepEditors.push(typeName);
+                                       }
+                               }
+                       }
+                       
+                       // clear editors
+                       _editors.forEach(function(editor, typeName) {
+                               if (keepEditors.indexOf(typeName) === -1) {
+                                       UiPageAction.remove('wcfClipboard-' + typeName);
+                                       
+                                       _editorDropdowns.get(typeName).innerHTML = '';
+                               }
+                       });
+                       
+                       // no items
                        if (!data.returnValues || !data.returnValues.items) {
                                return;
                        }
                        
                        // rebuild editors
-                       var fragment = document.createDocumentFragment();
-                       for (var typeName in data.returnValues.items) {
-                               if (!data.returnValues.items.hasOwnProperty(typeName) || !_editors.has(typeName)) {
+                       var created, dropdown, editor, typeData;
+                       var divider, item, itemData, itemIndex, label, unmarkAll;
+                       for (typeName in data.returnValues.items) {
+                               if (!data.returnValues.items.hasOwnProperty(typeName)) {
                                        continue;
                                }
                                
-                               var typeData = data.returnValues.items[typeName];
+                               typeData = data.returnValues.items[typeName];
+                               created = false;
                                
-                               var editor = _editors.get(typeName);
-                               var lists = DomTraverse.childrenByTag(editor, 'UL');
-                               var list = lists[0] || null;
-                               if (list === null) {
-                                       list = elCreate('ul');
+                               editor = _editors.get(typeName);
+                               dropdown = _editorDropdowns.get(typeName);
+                               if (editor === undefined) {
+                                       created = true;
+                                       
+                                       editor = elCreate('a');
+                                       editor.className = 'dropdownToggle';
+                                       editor.textContent = typeData.label;
+                                       
+                                       _editors.set(typeName, editor);
+                                       
+                                       dropdown = elCreate('ol');
+                                       dropdown.className = 'dropdownMenu';
+                                       
+                                       _editorDropdowns.set(typeName, dropdown);
+                               }
+                               else {
+                                       editor.textContent = typeData.label;
                                }
-                               
-                               fragment.appendChild(list);
-                               
-                               var listItem = elCreate('li');
-                               listItem.classList.add('dropdown');
-                               list.appendChild(listItem);
-                               
-                               var toggleButton = elCreate('span');
-                               toggleButton.className = 'dropdownToggle button';
-                               toggleButton.textContent = typeData.label;
-                               listItem.appendChild(toggleButton);
-                               
-                               var itemList = elCreate('ol');
-                               itemList.classList.add('dropdownMenu');
                                
                                // create editor items
-                               for (var itemIndex in typeData.items) {
+                               for (itemIndex in typeData.items) {
                                        if (!typeData.items.hasOwnProperty(itemIndex)) continue;
                                        
-                                       var itemData = typeData.items[itemIndex];
+                                       itemData = typeData.items[itemIndex];
                                        
-                                       var item = elCreate('li');
-                                       var label = elCreate('span');
+                                       item = elCreate('li');
+                                       label = elCreate('span');
                                        label.textContent = itemData.label;
                                        item.appendChild(label);
-                                       itemList.appendChild(item);
+                                       dropdown.appendChild(item);
                                        
                                        elData(item, 'type', typeName);
                                        item.addEventListener('click', _callbackItem);
@@ -508,23 +487,28 @@ define(
                                        _itemData.set(item, itemData);
                                }
                                
-                               var divider = elCreate('li');
+                               divider = elCreate('li');
                                divider.classList.add('dropdownDivider');
-                               itemList.appendChild(divider);
+                               dropdown.appendChild(divider);
                                
                                // add 'unmark all'
-                               var unmarkAll = elCreate('li');
+                               unmarkAll = elCreate('li');
                                elData(unmarkAll, 'type', typeName);
-                               var label = elCreate('span');
+                               label = elCreate('span');
                                label.textContent = Language.get('wcf.clipboard.item.unmarkAll');
                                unmarkAll.appendChild(label);
-                               itemList.appendChild(unmarkAll);
-                               listItem.appendChild(itemList);
-                               
                                unmarkAll.addEventListener('click', _callbackUnmarkAll);
-                               editor.appendChild(fragment);
+                               dropdown.appendChild(unmarkAll);
+                               
+                               if (keepEditors.indexOf(typeName) !== -1) {
+                                       UiPageAction.add('wcfClipboard-' + typeName, editor);
+                               }
                                
-                               UiSimpleDropdown.init(toggleButton, false);
+                               if (created) {
+                                       editor.parentNode.classList.add('dropdown');
+                                       editor.parentNode.appendChild(dropdown);
+                                       UiSimpleDropdown.init(editor);
+                               }
                        }
                },
                
@@ -532,7 +516,7 @@ define(
                 * Rebuilds the mark state for each item.
                 * 
                 * @param       {object<string, *>}     data            container data
-                * @param       {array<integer>}        objectIds       item object ids
+                * @param       {array<int>}            objectIds       item object ids
                 */
                _rebuildMarkings: function(data, objectIds) {
                        var markAll = true;
@@ -553,6 +537,4 @@ define(
                        }
                }
        };
-       
-       return ControllerClipboard;
 });
index 7d7949ea5636dc15a4ecde9b8b57626734dcd4a0..6839801b380e37bd6492ccaac7a77007c155f399 100644 (file)
@@ -46,14 +46,14 @@ define(['Ajax', 'Dictionary', 'Environment', 'Dom/ChangeListener', 'Dom/Util', '
                        }
                        
                        _popover = elCreate('div');
-                       _popover.classList.add('popover');
+                       _popover.className = 'popover forceHide';
                        
                        _popoverContent = elCreate('div');
-                       _popoverContent.classList.add('popoverContent');
+                       _popoverContent.className = 'popoverContent';
                        _popover.appendChild(_popoverContent);
                        
                        var pointer = elCreate('span');
-                       pointer.classList.add('elementPointer');
+                       pointer.className = 'elementPointer';
                        pointer.appendChild(elCreate('span'));
                        _popover.appendChild(pointer);
                        
@@ -357,6 +357,7 @@ define(['Ajax', 'Dictionary', 'Environment', 'Dom/ChangeListener', 'Dom/Util', '
                                return;
                        }
                        
+                       _popover.classList.remove('forceHide');
                        _popover.classList.add('active');
                        
                        UiAlignment.set(_popover, _elements.get(_activeId).element, {
index 7615f402e7ade054d4438a458421e897247a056e..90a562dc3740b01a213f117404b25d957d902f27 100644 (file)
@@ -311,8 +311,13 @@ define(
                 */
                _onScroll: function() {
                        _dropdowns.forEach((function(dropdown, containerId) {
-                               if (elData(dropdown, 'is-overlay-dropdown-button') === true && dropdown.classList.contains('dropdownOpen')) {
-                                       this.setAlignment(dropdown, _menus.get(containerId));
+                               if (dropdown.classList.contains('dropdownOpen')) {
+                                       if (elDataBool(dropdown, 'is-overlay-dropdown-button')) {
+                                               this.setAlignment(dropdown, _menus.get(containerId));
+                                       }
+                                       else {
+                                               this.close(containerId);
+                                       }
                                }
                        }).bind(this));
                },
diff --git a/wcfsetup/install/files/js/WoltLab/WCF/Ui/Page/Action.js b/wcfsetup/install/files/js/WoltLab/WCF/Ui/Page/Action.js
new file mode 100644 (file)
index 0000000..ddf7254
--- /dev/null
@@ -0,0 +1,145 @@
+/**
+ * Provides page actions such as "jump to top" and clipboard actions.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2016 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @module     WoltLab/WCF/Ui/Page/Action
+ */
+define(['Dictionary', 'Dom/Util'], function(Dictionary, DomUtil) {
+       "use strict";
+       
+       var _buttons = new Dictionary();
+       var _container = null;
+       var _didInit = false;
+       
+       /**
+        * @exports     WoltLab/WCF/Ui/Page/Action
+        */
+       return {
+               /**
+                * Initializes the page action container.
+                */
+               setup: function() {
+                       _didInit = true;
+                       
+                       _container = elCreate('ul');
+                       _container.className = 'pageAction';
+                       document.body.appendChild(_container);
+               },
+               
+               /**
+                * 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) {
+                       if (_didInit === false) this.setup();
+                       
+                       var listItem = elCreate('li');
+                       listItem.appendChild(button);
+                       elAttr(listItem, 'aria-hidden', (buttonName === 'toTop' ? 'true' : 'false'));
+                       elData(listItem, 'name', buttonName);
+                       
+                       // force 'to top' button to be always at the most outer position
+                       if (buttonName === 'toTop') {
+                               listItem.className = 'toTop initiallyHidden';
+                               _container.appendChild(listItem);
+                       }
+                       else {
+                               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) {
+                                       DomUtil.prepend(listItem, _container);
+                               }
+                               else {
+                                       _container.insertBefore(listItem, insertBefore);
+                               }
+                       }
+                       
+                       _buttons.set(buttonName, button);
+                       this._renderContainer();
+               },
+               
+               /**
+                * 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;
+                               listItem.addEventListener('animationend', function () {
+                                       _container.removeChild(listItem);
+                                       _buttons.delete(buttonName);
+                               });
+                                       
+                               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');
+                               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');
+               }
+       };
+});
diff --git a/wcfsetup/install/files/js/WoltLab/WCF/Ui/Page/JumpToTop.js b/wcfsetup/install/files/js/WoltLab/WCF/Ui/Page/JumpToTop.js
new file mode 100644 (file)
index 0000000..41e82bd
--- /dev/null
@@ -0,0 +1,81 @@
+/**
+ * Provides a link to scroll to top once the page is scrolled by at least 50% the height of the window.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2015 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @module     WoltLab/WCF/Ui/Page/JumpToTop
+ */
+define(['Environment', 'Language', './Action'], function(Environment, Language, PageAction) {
+       "use strict";
+       
+       /**
+        * @constructor
+        */
+       function JumpToTop() { this.init(); }
+       JumpToTop.prototype = {
+               /**
+                * Initializes the top link for desktop browsers only.
+                */
+               init: function() {
+                       // top link is not available on smartphones and tablets (they have a built-in function to accomplish this)
+                       if (Environment.platform() !== 'desktop') {
+                               return;
+                       }
+                       
+                       this._callbackScrollEnd = this._afterScroll.bind(this);
+                       this._timeoutScroll = null;
+                       
+                       var button = elCreate('a');
+                       button.className = 'jsTooltip';
+                       button.href = '#';
+                       elAttr(button, 'title', Language.get('wcf.global.scrollUp'));
+                       button.innerHTML = '<span class="icon icon16 fa-arrow-up"></span>';
+                       
+                       button.addEventListener('click', this._jump.bind(this));
+                       
+                       PageAction.add('toTop', button);
+                       
+                       window.addEventListener('scroll', this._scroll.bind(this));
+                       
+                       // invoke callback on page load
+                       this._afterScroll();
+               },
+               
+               /**
+                * Handles clicks on the top link.
+                * 
+                * @param       {Event}         event   event object
+                * @protected
+                */
+               _jump: function(event) {
+                       event.preventDefault();
+               },
+               
+               /**
+                * Callback executed whenever the window is being scrolled.
+                * 
+                * @protected
+                */
+               _scroll: function() {
+                       if (this._timeoutScroll !== null) {
+                               window.clearTimeout(this._timeoutScroll);
+                       }
+                       
+                       this._timeoutScroll = window.setTimeout(this._callbackScrollEnd, 100);
+               },
+               
+               /**
+                * Delayed callback executed once the page has not been scrolled for a certain amount of time.
+                * 
+                * @protected
+                */
+               _afterScroll: function() {
+                       this._timeoutScroll = null;
+                       
+                       PageAction[(window.scrollY >= window.innerHeight / 2) ? 'show' : 'hide']('toTop');
+               }
+       };
+       
+       return JumpToTop;
+});
index cf4d87a3f89763aee5205f969e6ba77be1db7cfb..bd579569a405cbf4cd3595925e472a77776db5a8 100644 (file)
@@ -44,6 +44,7 @@ define(['Environment', 'Dom/ChangeListener', 'Ui/Alignment'], function(Environme
                        this.init();
                        
                        DomChangeListener.add('WoltLab/WCF/Ui/Tooltip', this.init.bind(this));
+                       window.addEventListener('scroll', this._mouseLeave.bind(this));
                },
                
                /**
index e370c9c2b4b3de2a8950b4d83eb5e3a2b46d18ae..bdedf7f9f9fd05b6d705cb1da33d9398e593db31 100644 (file)
@@ -1,16 +1,10 @@
-/* container for both regular and interactive dropdowns */
 .dropdownMenuContainer {
-       /* force positioning in the upper left corner to prevent elements from
-          jumping during calculation */
+       bottom: 0;
        left: 0;
+       pointer-events: none;
        position: absolute;
        right: 0;
        top: 0;
-       
-       > .dropdown {
-               /* hide dropdown during calculation */
-               left: -200%;
-       }
 }
 
 .dropdown {
@@ -33,6 +27,7 @@
        }
        
        &.preInput {
+               // TODO: use flex-box instead
                display: table;
                width: 100%;
                
@@ -69,7 +64,7 @@
                                word-wrap: normal;
                                
                                &.active::after {
-                                       content: "\f0d7"; // @TODO: use a variable instead
+                                       content: "\f0d7"; // TODO: use a variable instead
                                        font-family: FontAwesome;
                                        font-size: 14px;
                                        margin-left: 7px;
        float: left;
        min-width: 160px;
        padding: 3px 0;
+       pointer-events: all;
        position: absolute;
        text-align: left;
+       visibility: hidden;
        z-index: 450;
        
        @include boxShadow(2px, 2px, rgba(0, 0, 0, .2), 10px);
        
        &.dropdownOpen {
                display: block;
+               visibility: visible;
        }
        
        li {
index 38add1a5d61ece027a11437c572ee29ebd3bd619..435d9a202fd6d17ba248e7d9f9933b8fbf19032f 100644 (file)
@@ -1,6 +1,5 @@
-.dropdownMenuContainer > .interactiveDropdown {
-       /* hide dropdown during calculation */
-       left: -200%;
+.dropdownMenuContainer > .interactiveDropdown.open {
+       visibility: visible;
 }
 
 /* styling for interactive dropdowns (currently only used in the user panel) */
@@ -9,13 +8,11 @@
        box-shadow: 2px 2px 10px 0 rgba(0, 0, 0, .2);
        color: $wcfContentText;
        display: block;
+       pointer-events: all;
        position: absolute;
+       visibility: hidden;
        z-index: 450;
        
-       &:not(.open) {
-               display: none !important;
-       }
-       
        > .elementPointer {
                border: 10px solid transparent;
                //border-bottom-color: $wcfDropdownBorder;
diff --git a/wcfsetup/install/files/style/ui/pageAction.scss b/wcfsetup/install/files/style/ui/pageAction.scss
new file mode 100644 (file)
index 0000000..3330b0e
--- /dev/null
@@ -0,0 +1,68 @@
+@keyframes wcfPageAction {
+       0%   { visibility: visible; transform: translateY(-10px); opacity: 0; }
+       100% { visibility: visible; transform: translateY(0);     opacity: 1; }
+}
+
+@keyframes wcfPageActionOut {
+       0%   { visibility: visible; transform: translateY(0);     opacity: 1; }
+       100% { visibility: hidden;  transform: translateY(-10px); opacity: 0; }
+}
+
+@keyframes wcfPageActionRemove {
+       0%   { visibility: visible; transform: translateY(0);     opacity: 1; }
+       60%  { visibility: hidden;  transform: translateY(-10px); opacity: 0; }
+       100% { visibility: hidden;  transform: translateY(-10px); opacity: 0; max-width: 0; }
+}
+
+.pageAction {
+       bottom: 20px;
+       position: fixed;
+       right: 20px;
+       z-index: 400;
+       
+       @extend .inlineList;
+       
+       > li {
+               animation: wcfPageActionOut .3s;
+               animation-fill-mode: forwards;
+               
+               // required to animate 'max-width' properly when removing items
+               max-width: 400px;
+               white-space: nowrap;
+               
+               &[aria-hidden="false"] {
+                       animation: wcfPageAction .3s;
+                       animation-fill-mode: forwards;
+               }
+               
+               &.remove {
+                       animation: wcfPageActionRemove .5s;
+                       animation-fill-mode: forwards;
+               }
+               
+               &.initiallyHidden {
+                       animation: 0;
+                       visibility: hidden;
+               }
+               
+               > a {
+                       background-color: rgba(0, 0, 0, .6);
+                       color: rgba(224, 224, 224, 1);
+                       padding: 5px 10px;
+                       
+                       > .icon {
+                               color: rgba(224, 224, 224, 1);
+                       }
+               }
+               
+               > a:hover,
+               &.dropdownOpen > a {
+                       background-color: rgba(0, 0, 0, 1);
+                       color: rgba(255, 255, 255, 1);
+                       
+                       > .icon {
+                               color: rgba(255, 255, 255, 1);
+                       }
+               }
+       }
+}
index a881863b3e634ca6532d82258e96c3bbf561d551..338a733340599362e4b2f9f76388a51a2b0b55af 100644 (file)
@@ -18,6 +18,7 @@
        position: absolute;
        top: 0;
        vertical-align: middle;
+       visibility: hidden;
        width: 400px !important;
        z-index: 500;