{include file='pageFooter'}
</div>
-
+
+{include file='pageMenuMobile'}
+
{event name='footer'}
<!-- JAVASCRIPT_RELOCATE_POSITION -->
{js application='wcf' file='require.config' bundle='WCF.Core' core='true'}
{js application='wcf' file='require.linearExecution' bundle='WCF.Core' core='true'}
{js application='wcf' file='wcf.globalHelper' bundle='WCF.Core' core='true'}
+{js application='wcf' file='closest' bundle='WCF.Core' core='true'}
<script>
requirejs.config({
baseUrl: '{@$__wcf->getPath()}js'
-<div id="logo" class="logo">
+<div id="pageHeaderLogo" class="pageHeaderLogo">
{if MODULE_WCF_AD && $__disableAds|empty}{@$__wcf->getAdHandler()->getAds('com.woltlab.wcf.logo')}{/if}
<a href="{link}{/link}">
{* @TODO *}
- <img src="{@$__wcf->getPath()}images/default-logo.png" alt="" class="large">
- <img src="{@$__wcf->getPath()}images/default-logo-small.png" alt="" class="small">
+ <img src="{@$__wcf->getPath()}images/default-logo.png" alt="" class="pageHeaderLogoLarge">
+ <img src="{@$__wcf->getPath()}images/default-logo-small.png" alt="" class="pageHeaderLogoSmall">
{*if $__wcf->getStyleHandler()->getStyle()->getPageLogo()}
<img src="{$__wcf->getStyleHandler()->getStyle()->getPageLogo()}" alt="">
{/if*}
--- /dev/null
+{* main menu / page options / breadcrumbs *}
+<div id="pageMainMenuMobile" class="pageMainMenuMobile menuOverlayMobile" data-page-logo="{$__wcf->getPath()}images/default-logo.png">
+ <ol class="menuOverlayItemList" data-title="TODO: menu">
+ <li class="menuOverlayTitle">TODO: menu</li>
+ <li class="menuOverlayItem">
+ <a href="#" class="menuOverlayItemLink box24">
+ <span class="icon icon24 fa-sitemap"></span>
+ <span class="menuOverlayItemTitle">TODO: navigation</span>
+ </a>
+ <ol class="menuOverlayItemList">
+ {foreach from=$__wcf->getBoxHandler()->getBoxes('mainMenu')[0]->getMenu()->getMenuItemNodeList() item=menuItemNode}
+ <li class="menuOverlayItem">
+ {assign var=__outstandingItems value=$menuItemNode->getMenuItem()->getOutstandingItems()}
+ <a href="{$menuItemNode->getMenuItem()->getURL()}" class="menuOverlayItemLink{if $__outstandingItems} menuOverlayItemBadge{/if}">
+ <span class="menuOverlayItemTitle">{lang}{$menuItemNode->getMenuItem()->title}{/lang}</span>
+ {if $__outstandingItems}
+ <span class="badge badgeInverse">{#$__outstandingItems}</span>
+ {/if}
+ </a>
+
+ {if $menuItemNode->hasChildren()}<ol class="menuOverlayItemList">{else}</li>{/if}
+
+ {if !$menuItemNode->hasChildren() && $menuItemNode->isLastSibling()}
+ {@"</ol></li>"|str_repeat:$menuItemNode->getOpenParentNodes()}
+ {/if}
+ {/foreach}
+ </ol>
+ </li>
+ {hascontent}
+ <li class="menuOverlayItem">
+ <a href="#" class="menuOverlayItemLink box24">
+ <span class="icon icon24 fa-gears"></span>
+ <span class="menuOverlayItemTitle">TODO: page options</span>
+ </a>
+ <ol class="menuOverlayItemList">
+ {content}
+ {if !$__pageOptions|empty}
+ {@$__pageOptions}
+ {/if}
+
+ {event name='pageOptions'}
+ {/content}
+ </ol>
+ </li>
+ {/hascontent}
+ {hascontent}
+ <li class="menuOverlayTitle">TODO: current location</li>
+ <li class="menuOverlayItem">
+ <a href="#" class="menuOverlayItemLink box24">
+ <span class="icon icon24 fa-cogs"></span>
+ <span class="menuOverlayItemTitle">TODO: current location</span>
+ </a>
+ <ol class="menuOverlayItemList">
+ {content}
+ {assign var=__breadcrumbsDepth value=0}
+ {foreach from=$__wcf->getBreadcrumbs() item=$breadcrumb}
+ <li class="menuOverlayItem">
+ <a href="{$breadcrumb->getURL()}" class="menuOverlayItemLink">
+ <span class="menuOverlayItemTitle"{if $__breadcrumbsDepth} style="padding-left: {$__breadcrumbsDepth * 10}px" {/if}>
+ <span class="icon icon24 fa-{if $__breadcrumbsDepth}caret-right{else}home{/if}"></span>
+ {$breadcrumb->getLabel()}
+ </span>
+ </a>
+ </li>
+ {assign var=__breadcrumbsDepth value=$__breadcrumbsDepth + 1}
+ {/foreach}
+ {/content}
+ </ol>
+ </li>
+ {/hascontent}
+ </ol>
+ </li>
+ </ol>
+</div>
+
+{* user menu *}
+{* TODO: guests should see the login overlay when clicking the button *}
+<div id="pageUserMenuMobile" class="pageUserMenuMobile menuOverlayMobile" data-page-logo="{$__wcf->getPath()}images/default-logo.png">
+ <ol class="menuOverlayItemList" data-title="TODO: user menu">
+ <li class="menuOverlayTitle">{lang}wcf.user.controlPanel{/lang}</li>
+ <li class="menuOverlayItem">
+ <a href="{link controller='User' object=$__wcf->user}{/link}" class="menuOverlayItemLink box24">
+ {@$__wcf->getUserProfileHandler()->getAvatar()->getImageTag(24)}
+ <span class="menuOverlayItemTitle">{$__wcf->user->username}</span>
+ </a>
+ </li>
+ <li class="menuOverlayItem">
+ <a href="{link controller='Settings'}{/link}" class="menuOverlayItemLink box24">
+ <span class="icon icon24 fa-cog"></span>
+ <span class="menuOverlayItemTitle">Einstellungen</span>
+ </a>
+ <ol class="menuOverlayItemList">
+ {foreach from=$__wcf->getUserMenu()->getMenuItems('') item=menuCategory}
+ <li class="menuOverlayTitle">{lang}{$menuCategory->menuItem}{/lang}</li>
+ {foreach from=$__wcf->getUserMenu()->getMenuItems($menuCategory->menuItem) item=menuItem}
+ <li class="menuOverlayItem">
+ <a href="{$menuItem->getProcessor()->getLink()}" class="menuOverlayItemLink">{@$menuItem}</a>
+ </li>
+ {/foreach}
+ {/foreach}
+ </ol>
+ </li>
+ {if $__wcf->session->getPermission('admin.general.canUseAcp')}
+ <li class="menuOverlayItem">
+ <a href="{link isACP=true}{/link}" class="menuOverlayItemLink box24">
+ <span class="icon icon24 fa-wrench"></span>
+ <span class="menuOverlayItemTitle">{lang}wcf.global.acp.short{/lang}</span>
+ </a>
+ </li>
+ {/if}
+ <li class="menuOverlayItemSpacer"></li>
+ <li class="menuOverlayItem" data-more="com.woltlab.wcf.notifications">
+ <a href="{link controller='NotificationList'}{/link}" class="menuOverlayItemLink box24">
+ <span class="icon icon24 fa-bell-o"></span>
+ <span class="menuOverlayItemTitle">{lang}wcf.user.notification.notifications{/lang}</span>
+ </a>
+ </li>
+ <li class="menuOverlayItem">
+ <a href="#" class="menuOverlayItemLink box24">
+ <span class="icon icon24 fa-exclamation-triangle"></span>
+ <span class="menuOverlayItemTitle">{lang}wcf.moderation.moderation{/lang}</span>
+ </a>
+ </li>
+
+ {event name='userMenuItems'}
+
+ <li class="menuOverlayItemSpacer"></li>
+ <li class="menuOverlayItem">
+ <a href="{link controller='Logout'}t={@SECURITY_TOKEN}{/link}" class="menuOverlayItemLink box24">
+ <span class="icon icon24 fa-sign-out"></span>
+ <span class="menuOverlayItemTitle">{lang}wcf.user.logout{/lang}</span>
+ </a>
+ </li>
+ </ol>
+</div>
WCF.DOMNodeInsertedHandler.execute();
- enquire.register('screen and (max-width: 800px)', {
+ enquire.register('(max-width: 767px)', {
match: $.proxy(this._enableMobileView, this),
unmatch: $.proxy(this._disableMobileView, this)
});
// fix maps in mobile sidebars by refreshing the map when displaying
// the map
if (this._mapContainer.parents('.sidebar').length) {
- enquire.register('screen and (max-width: 800px)', {
+ enquire.register('(max-width: 767px)', {
setup: $.proxy(this._addSidebarMapListener, this),
deferSetup: true
});
/**
* Toggles the interactive dropdown.
*
- * @param object event
- * @return boolean
+ * @param {Event=} event
+ * @return {boolean}
*/
toggle: function(event) {
- event.preventDefault();
+ if (event instanceof Event) {
+ event.preventDefault();
+ }
if (this._dropdown === null) {
this._dropdown = this._initDropdown();
}
WCF.System.PushNotification.addCallback('userNotificationCount', $.proxy(this.updateUserNotificationCount, this));
+
+ require(['EventHandler'], (function(EventHandler) {
+ EventHandler.add('com.woltlab.wcf.UserMenuMobile', 'more', (function(data) {
+ console.debug("called");
+ console.debug(data);
+ if (data.identifier === 'com.woltlab.wcf.notifications') {
+ this.toggle();
+
+ //data.handler.close(true);
+ }
+ }).bind(this));
+ }).bind(this));
},
/**
* Renders the dropdown.
*/
render: function() {
- var $pageDirection = WCF.Language.get('wcf.global.pageDirection');
-
- if ($('html').css('caption-side') === 'bottom') {
- this._renderMobile($pageDirection);
+ if (window.matchMedia('(max-width: 767px)').matches) {
+ this._container.css({
+ bottom: '',
+ left: '',
+ right: '',
+ top: elById('pageHeader').clientHeight + 'px'
+ });
}
else {
- this._renderDesktop($pageDirection);
+ require(['Ui/Alignment'], (function(UiAlignment) {
+ UiAlignment.set(this._container[0], this._triggerElement[0], {
+ pointer: true
+ });
+ }).bind(this));
}
},
suppressScrollX: true
});
}
- },
-
- /**
- * Renders the dropdown on mobile devices.
- *
- * @param string pageDirection
- */
- _renderMobile: function(pageDirection) {
- var $elementDimensions = this._triggerElement.getDimensions('outer');
- var $elementHalfWidth = Math.floor($elementDimensions.width / 2);
- var $elementOffsets = this._triggerElement.getOffsets('offset');
- var $pointerHalfWidth = Math.floor(this._pointer.outerWidth() / 2);
-
- this._container.css({
- top: $elementOffsets.top + $elementDimensions.height + 'px'
- });
-
- this._pointer.css({
- left: ($elementOffsets.left + $elementHalfWidth) - $pointerHalfWidth + 'px'
- });
- },
-
- /**
- * Renders the dropdown on desktops.
- *
- * @param string pageDirection
- */
- _renderDesktop: function(pageDirection) {
- require(['Ui/Alignment'], (function(UiAlignment) {
- UiAlignment.set(this._container[0], this._triggerElement[0], {
- pointer: true
- });
- }).bind(this));
}
});
* Provides helper functions to traverse the DOM.
*
* @author Alexander Ebert
- * @copyright 2001-2015 WoltLab GmbH
+ * @copyright 2001-2016 WoltLab GmbH
* @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
* @module WoltLab/WCF/Dom/Traverse
*/
-define(['Dom/Util'], function(DomUtil) {
+define([], function() {
"use strict";
/** @const */ var NONE = 0;
var _probe = [
function(el, none) { return true; },
- function(el, selector) { return DomUtil.matches(el, selector); },
+ function(el, selector) { return el.matches(selector); },
function(el, className) { return el.classList.contains(className); },
function(el, tagName) { return el.nodeName === tagName; }
];
/**
* @exports WoltLab/WCF/Dom/Traverse
*/
- var DomTraverse = {
+ return {
/**
* Examines child elements and returns the first child matching the given selector.
*
* Returns the next element sibling with given CSS class.
*
* @param {Element} el element
- * @param {string} className CSS class name
+ * @param {string} tagName element tag name
* @return {(Element|null)} null if there is no next sibling element or it does not have the class set
*/
nextByTag: function(el, tagName) {
- return _sibling(el, 'nextElementSibling', CLASS_NAME, className);
+ return _sibling(el, 'nextElementSibling', TAG_NAME, tagName);
},
/**
* Returns the previous element sibling with given CSS class.
*
* @param {Element} el element
- * @param {string} className CSS class name
+ * @param {string} tagName element tag name
* @return {(Element|null)} null if there is no previous sibling element or it does not have the class set
*/
prevByTag: function(el, tagName) {
- return _sibling(el, 'previousElementSibling', CLASS_NAME, className);
+ return _sibling(el, 'previousElementSibling', TAG_NAME, tagName);
}
};
-
- return DomTraverse;
});
define(['StringUtil'], function(StringUtil) {
"use strict";
- var _matchesSelectorFunction = '';
- var _possibleFunctions = ['matches', 'webkitMatchesSelector', 'mozMatchesSelector', 'msMatchesSelector'];
- for (var i = 0; i < 4; i++) {
- if (Element.prototype.hasOwnProperty(_possibleFunctions[i])) {
- _matchesSelectorFunction = _possibleFunctions[i];
- break;
- }
- }
-
function _isBoundaryNode(element, ancestor, position) {
if (!ancestor.contains(element)) {
throw new Error("Ancestor element does not contain target element.");
* @return {string} element id
*/
identify: function(el) {
- if (!el || !(el instanceof Element)) {
- return null;
+ if (!(el instanceof Element)) {
+ throw new TypeError("Expected a valid DOM element as argument.");
}
var id = elAttr(el, 'id');
return id;
},
- /**
- * Returns true if element matches given CSS selector.
- *
- * @param {Element} el element
- * @param {string} selector CSS selector
- * @return {boolean} true if element matches selector
- */
- matches: function(el, selector) {
- return el[_matchesSelectorFunction](selector);
- },
-
/**
* Returns the outer height of an element including margins.
*
* Provides basic details on the JavaScript environment.
*
* @author Alexander Ebert
- * @copyright 2001-2015 WoltLab GmbH
+ * @copyright 2001-2016 WoltLab GmbH
* @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
* @module WoltLab/WCF/Environment
*/
/**
* @exports WoltLab/WCF/Enviroment
*/
- var Environment = {
+ return {
/**
* Determines environment variables.
*/
return _touch;
}
};
-
- return Environment;
});
* @constructor
*/
function Template(template) {
- // Fetch Language, as it cannot be provided because of a circular dependency
+ // Fetch Language/StringUtil, as it cannot be provided because of a circular dependency
if (Language === undefined) Language = require('Language');
+ if (StringUtil === undefined) StringUtil = require('StringUtil');
try {
template = parser.parse(template);
return true;
}).bind(this);
- enquire.register('screen and (max-width: 800px)', {
+ enquire.register('(max-width: 767px)', {
match: function() { _dialogFullHeight = true; },
unmatch: function() { _dialogFullHeight = false; },
setup: function() { _dialogFullHeight = true; },
* Modifies the interface to provide a better usability for mobile devices.
*
* @author Alexander Ebert
- * @copyright 2001-2015 WoltLab GmbH
+ * @copyright 2001-2016 WoltLab GmbH
* @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
* @module WoltLab/WCF/Ui/Mobile
*/
define(
- [ 'enquire', 'Environment', 'Language', 'Dom/ChangeListener', 'Dom/Traverse', 'Ui/CloseOverlay'],
- function(enquire, Environment, Language, DomChangeListener, DomTraverse, UiCloseOverlay)
+ [ 'Environment', 'Language', 'Dom/ChangeListener', 'Ui/CloseOverlay', 'Ui/Screen', './Page/Menu/Main', './Page/Menu/User'],
+ function(Environment, Language, DomChangeListener, UiCloseOverlay, UiScreen, UiPageMenuMain, UiPageMenuUser)
{
"use strict";
var _buttonGroupNavigations = null;
var _enabled = false;
var _main = null;
+ var _pageMenuMain = null;
+ var _pageMenuUser = null;
var _sidebar = null;
/**
* @exports WoltLab/WCF/Ui/Mobile
*/
- var UiMobile = {
+ return {
/**
* Initializes the mobile UI using enquire.js.
*/
document.documentElement.classList.add('mobile');
}
- enquire.register('screen and (max-width: 800px)', {
- match: this.enable.bind(this),
- unmatch: this.disable.bind(this),
- setup: this._init.bind(this),
- deferSetup: true
+ UiScreen.on({
+ small: this.enable.bind(this),
+ large: this.disable.bind(this),
+ setup: this._init.bind(this)
});
-
- if (Environment.browser() === 'microsoft' && _sidebar !== null && _sidebar.clientWidth > 305) {
- this._fixSidebarIE();
- }
},
/**
enable: function() {
_enabled = true;
- if (Environment.browser() === 'microsoft') this._fixSidebarIE();
+ _pageMenuMain.enable();
+ _pageMenuUser.enable();
},
/**
disable: function() {
_enabled = false;
- if (Environment.browser() === 'microsoft') this._fixSidebarIE();
- },
-
- _fixSidebarIE: function() {
- if (_sidebar === null) return;
-
- // sidebar is rarely broken on IE9/IE10
- _sidebar.style.setProperty('display', 'none');
- _sidebar.style.removeProperty('display');
+ _pageMenuMain.disable();
+ _pageMenuUser.disable();
},
_init: function() {
- this._initSidebarToggleButtons();
- this._initSearchBar();
+ //this._initSidebarToggleButtons();
+ //this._initSearchBar();
this._initButtonGroupNavigation();
+ this._initMobileMenu();
UiCloseOverlay.add('WoltLab/WCF/Ui/Mobile', this._closeAllMenus.bind(this));
DomChangeListener.add('WoltLab/WCF/Ui/Mobile', this._initButtonGroupNavigation.bind(this));
span.className = 'icon icon24 fa-list';
button.appendChild(span);
- button.addEventListener('click', function(ev) {
- var next = DomTraverse.next(button);
- if (next !== null) {
- next.classList.toggle('open');
+ (function(button) {
+ button.addEventListener('click', function(ev) {
+ var next = button.nextElementSibling;
+ if (next !== null) {
+ next.classList.toggle('open');
+
+ ev.stopPropagation();
+ return false;
+ }
- ev.stopPropagation();
- return false;
- }
-
- return true;
- });
+ return true;
+ });
+ })(button);
navigation.insertBefore(button, navigation.firstChild);
}
},
+ _initMobileMenu: function() {
+ _pageMenuMain = new UiPageMenuMain();
+ _pageMenuUser = new UiPageMenuUser();
+ },
+
_closeAllMenus: function() {
var openMenus = elBySelAll('.jsMobileButtonGroupNavigation > ul.open');
for (var i = 0, length = openMenus.length; i < length; i++) {
}
}
};
-
- return UiMobile;
});
--- /dev/null
+/**
+ * Provides a touch-friendly fullscreen menu.
+ *
+ * @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/Menu/Abstract
+ */
+define(['Environment', 'EventHandler', 'ObjectMap', 'Dom/Traverse', 'Ui/Screen'], function(Environment, EventHandler, ObjectMap, DomTraverse, UiScreen) {
+ "use strict";
+
+ /**
+ * @param {string} eventIdentifier event namespace
+ * @param {string} elementId menu element id
+ * @param {string} buttonSelector CSS selector for toggle button
+ * @constructor
+ */
+ function UiPageMenuAbstract(eventIdentifier, elementId, buttonSelector) { this.init(eventIdentifier, elementId, buttonSelector); }
+ UiPageMenuAbstract.prototype = {
+ /**
+ * Initializes a touch-friendly fullscreen menu.
+ *
+ * @param {string} eventIdentifier event namespace
+ * @param {string} elementId menu element id
+ * @param {string} buttonSelector CSS selector for toggle button
+ */
+ init: function(eventIdentifier, elementId, buttonSelector) {
+ this._enabled = true;
+ this._eventIdentifier = eventIdentifier;
+ this._items = new ObjectMap();
+ this._menu = elById(elementId);
+
+ var callbackOpen = this.open.bind(this);
+ var button = elBySel(buttonSelector);
+ button.addEventListener(WCF_CLICK_EVENT, callbackOpen);
+
+ this._initItems();
+ this._initHeader();
+
+ EventHandler.add(this._eventIdentifier, 'open', callbackOpen);
+ EventHandler.add(this._eventIdentifier, 'close', this.close.bind(this));
+
+ var itemList, itemLists = elByClass('menuOverlayItemList', this._menu);
+ this._menu.addEventListener('animationend', (function() {
+ if (!this._menu.classList.contains('open')) {
+ for (var i = 0, length = itemLists.length; i < length; i++) {
+ itemList = itemLists[i];
+
+ // force the main list to be displayed
+ itemList.classList.remove('active');
+ itemList.classList.remove('hidden');
+ }
+ }
+ }).bind(this));
+ },
+
+ /**
+ * Opens the menu.
+ *
+ * @param {Event} event event object
+ */
+ open: function(event) {
+ if (!this._enabled) {
+ return;
+ }
+
+ if (event instanceof Event) {
+ event.preventDefault();
+ }
+
+ this._menu.classList.add('enableAnimation');
+ this._menu.classList.add('open');
+
+ UiScreen.scrollDisable();
+ },
+
+ /**
+ * Closes the menu.
+ *
+ * @param {(Event|boolean)} event event object or boolean true to force close the menu
+ */
+ close: function(event) {
+ if (event instanceof Event) {
+ event.preventDefault();
+ }
+ else if (event === true) {
+ this._menu.classList.remove('enableAnimation');
+ }
+
+ if (this._menu.classList.contains('open')) {
+ this._menu.classList.remove('open');
+
+ UiScreen.scrollEnable();
+ }
+
+ },
+
+ /**
+ * Enables the touch menu.
+ */
+ enable: function() {
+ this._enabled = true;
+ },
+
+ /**
+ * Disables the touch menu.
+ */
+ disable: function() {
+ this._enabled = false;
+
+ this.close(true);
+ },
+
+ /**
+ * Initializes all menu items.
+ *
+ * @protected
+ */
+ _initItems: function() {
+ elBySelAll('.menuOverlayItemLink', this._menu, this._initItem.bind(this));
+ },
+
+ /**
+ * Initializes a single menu item.
+ *
+ * @param {Element} item menu item
+ * @protected
+ */
+ _initItem: function(item) {
+ var itemList = item.nextElementSibling;
+ if (itemList === null) {
+ return;
+ }
+
+ var isLink = (elAttr(item, 'href') !== '#');
+ var parent = item.parentNode;
+ var parentItemList = parent.parentNode;
+ var itemTitle = DomTraverse.childByClass(item, 'menuOverlayItemTitle').textContent;
+
+ this._items.set(item, {
+ itemList: itemList,
+ parentItemList: parentItemList
+ });
+
+ elData(itemList, 'title', itemTitle);
+
+ var callbackLink = this._showItemList.bind(this, item), wrapper;
+ if (isLink) {
+ wrapper = elCreate('span');
+ wrapper.className = 'menuOverlayItemWrapper';
+ parent.insertBefore(wrapper, item);
+ wrapper.appendChild(item);
+
+ var moreLink = elCreate('a');
+ elAttr(moreLink, 'href', '#');
+ moreLink.className = 'menuOverlayItemLinkIcon';
+ moreLink.innerHTML = '<span class="icon icon24 fa-angle-right"></span>';
+ moreLink.addEventListener(WCF_CLICK_EVENT, callbackLink);
+ wrapper.appendChild(moreLink);
+ }
+ else {
+ item.classList.add('menuOverlayItemLinkMore');
+ item.addEventListener(WCF_CLICK_EVENT, callbackLink);
+ }
+
+ var backLinkItem = elCreate('li');
+ backLinkItem.className = 'menuOverlayHeader';
+
+ wrapper = elCreate('span');
+ wrapper.className = 'menuOverlayItemWrapper';
+
+ var backLink = elCreate('a');
+ elAttr(backLink, 'href', '#');
+ backLink.className = 'menuOverlayItemLink menuOverlayBackLink';
+ backLink.textContent = elData(parentItemList, 'title');
+ backLink.addEventListener(WCF_CLICK_EVENT, this._hideItemList.bind(this, item));
+
+ var closeLink = elCreate('a');
+ elAttr(closeLink, 'href', '#');
+ closeLink.className = 'menuOverlayItemLinkIcon';
+ closeLink.innerHTML = '<span class="icon icon24 fa-times"></span>';
+ closeLink.addEventListener(WCF_CLICK_EVENT, this.close.bind(this));
+
+ wrapper.appendChild(backLink);
+ wrapper.appendChild(closeLink);
+ backLinkItem.appendChild(wrapper);
+
+ itemList.insertBefore(backLinkItem, itemList.firstElementChild);
+
+ if (!backLinkItem.nextElementSibling.classList.contains('menuOverlayTitle')) {
+ var titleItem = elCreate('li');
+ titleItem.className = 'menuOverlayTitle';
+ var title = elCreate('span');
+ title.textContent = itemTitle;
+ titleItem.appendChild(title);
+
+ itemList.insertBefore(titleItem, backLinkItem.nextElementSibling);
+ }
+ },
+
+ /**
+ * Renders the menu item list header.
+ *
+ * @protected
+ */
+ _initHeader: function() {
+ var listItem = elCreate('li');
+ listItem.className = 'menuOverlayHeader';
+
+ var wrapper = elCreate('span');
+ wrapper.className = 'menuOverlayItemWrapper';
+ listItem.appendChild(wrapper);
+
+ var logoWrapper = elCreate('span');
+ logoWrapper.className = 'menuOverlayLogoWrapper';
+ wrapper.appendChild(logoWrapper);
+
+ var logo = elCreate('span');
+ logo.className = 'menuOverlayLogo';
+ logo.style.setProperty('background-image', 'url("' + elData(this._menu, 'page-logo') + '")', '');
+ logoWrapper.appendChild(logo);
+
+ var closeLink = elCreate('a');
+ elAttr(closeLink, 'href', '#');
+ closeLink.className = 'menuOverlayItemLinkIcon';
+ closeLink.innerHTML = '<span class="icon icon24 fa-times"></span>';
+ closeLink.addEventListener(WCF_CLICK_EVENT, this.close.bind(this));
+ wrapper.appendChild(closeLink);
+
+ var list = DomTraverse.childByClass(this._menu, 'menuOverlayItemList');
+ list.insertBefore(listItem, list.firstElementChild);
+ },
+
+ /**
+ * Hides an item list, return to the parent item list.
+ *
+ * @param {Element} item menu item
+ * @param {Event} event event object
+ * @protected
+ */
+ _hideItemList: function(item, event) {
+ if (event instanceof Event) {
+ event.preventDefault();
+ }
+
+ var data = this._items.get(item);
+ data.itemList.classList.remove('active');
+ data.parentItemList.classList.remove('hidden');
+ },
+
+ /**
+ * Shows the child item list.
+ *
+ * @param {Element} item menu item
+ * @param event
+ * @private
+ */
+ _showItemList: function(item, event) {
+ if (event instanceof Event) {
+ event.preventDefault();
+ }
+
+ var data = this._items.get(item);
+
+ var load = elData(data.itemList, 'load');
+ if (load) {
+ if (!elDataBool(item, 'loaded')) {
+ var icon = event.currentTarget.firstElementChild;
+ if (icon.classList.contains('fa-angle-right')) {
+ icon.classList.remove('fa-angle-right');
+ icon.classList.add('fa-spinner');
+ }
+
+ EventHandler.fire(this._eventIdentifier, 'load_' + load);
+
+ return;
+ }
+ }
+
+ data.itemList.classList.add('active');
+ data.parentItemList.classList.add('hidden');
+ }
+ };
+
+ return UiPageMenuAbstract;
+});
--- /dev/null
+/**
+ * Provides the touch-friendly fullscreen main menu.
+ *
+ * @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/Menu/Main
+ */
+define(['Core', './Abstract'], function(Core, UiPageMenuAbstract) {
+ "use strict";
+
+ /**
+ * @constructor
+ */
+ function UiPageMenuMain() { this.init(); }
+ Core.inherit(UiPageMenuMain, UiPageMenuAbstract, {
+ /**
+ * Initializes the touch-friendly fullscreen main menu.
+ */
+ init: function() {
+ UiPageMenuMain._super.prototype.init.call(
+ this,
+ 'com.woltlab.wcf.MainMenuMobile',
+ 'pageMainMenuMobile',
+ '#pageHeader .mainMenu'
+ );
+ }
+ });
+
+ return UiPageMenuMain;
+});
--- /dev/null
+/**
+ * Provides the touch-friendly fullscreen user menu.
+ *
+ * @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/Menu/User
+ */
+define(['Core', 'EventHandler', './Abstract'], function(Core, EventHandler, UiPageMenuAbstract) {
+ "use strict";
+
+ /**
+ * @constructor
+ */
+ function UiPageMenuUser() { this.init(); }
+ Core.inherit(UiPageMenuUser, UiPageMenuAbstract, {
+ /**
+ * Initializes the touch-friendly fullscreen user menu.
+ */
+ init: function() {
+ UiPageMenuUser._super.prototype.init.call(
+ this,
+ 'com.woltlab.wcf.UserMenuMobile',
+ 'pageUserMenuMobile',
+ '#pageHeader .userPanel'
+ );
+ },
+
+ /**
+ * Overrides the `_initItem()` method to check for special items that do not
+ * act as a link but instead trigger an event for external processing.
+ *
+ * @param {Element} item menu item
+ * @protected
+ */
+ _initItem: function(item) {
+ // check if it should contain a 'more' link w/ an external callback
+ var more = elData(item.parentNode, 'more');
+ if (more) {
+ item.addEventListener(WCF_CLICK_EVENT, (function(event) {
+ event.preventDefault();
+ event.stopPropagation();
+
+ EventHandler.fire(this._eventIdentifier, 'more', {
+ handler: this,
+ identifier: more
+ });
+ }).bind(this));
+
+ return;
+ }
+
+ UiPageMenuUser._super.prototype._initItem.call(this, item);
+ }
+ });
+
+ return UiPageMenuUser;
+});
--- /dev/null
+/**
+ * Provides consistent support for media queries and body scrolling.
+ *
+ * @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/Screen
+ */
+define(['Core', 'Dictionary'], function(Core, Dictionary) {
+ "use strict";
+
+ var _bodyOverflow = '';
+ var _mql = new Dictionary();
+ var _scrollDisableCounter = 0;
+
+ /**
+ * @exports WoltLab/WCF/Ui/Screen
+ */
+ return {
+ /**
+ * Registers event listeners for media query match/unmatch.
+ *
+ * The `callbacks` object may contain the following keys:
+ * - `small` or `match`, triggered when media query matches
+ * - `large` or `unmatch`, triggered when media query no longer matches
+ * - `setup`, invoked when media query first matches
+ *
+ * The `small` and `large` keys only exist to increase readability when omitting
+ * the `query` argument and thus default to match the default value of `query`.
+ *
+ * `query` will default to `(max-width: 767px)`, it allows any value that can
+ * be evaluated with `window.matchMedia`.
+ *
+ * Returns a UUID that is used to internal identify the callbacks, can be used
+ * to remove binding by calling the `remove` method.
+ *
+ * @param {object} callbacks
+ * @param {string=} query
+ * @return {string} UUID for listener removal
+ */
+ on: function(callbacks, query) {
+ var uuid = Core.getUuid(), queryObject = this._getQueryObject(query);
+
+ if (typeof callbacks.small === 'function' || typeof callbacks.match === 'function') {
+ queryObject.callbacksMatch.set(uuid, callbacks.small || callbacks.match);
+ }
+
+ if (typeof callbacks.large === 'function' || typeof callbacks.unmatch === 'function') {
+ queryObject.callbacksUnmatch.set(uuid, callbacks.large || callbacks.unmatch);
+ }
+
+ if (typeof callbacks.setup === 'function') {
+ if (queryObject.mql.matches) {
+ callbacks.setup();
+ }
+ else {
+ queryObject.callbacksSetup.set(uuid, callbacks.setup);
+ }
+ }
+
+ return uuid;
+ },
+
+ /**
+ * Removes all listeners identified by their common UUID.
+ *
+ * @param {string} uuid UUID received when calling `on()`
+ * @param {string=} query must match the `query` argument used when calling `on()`
+ */
+ remove: function(uuid, query) {
+ var queryObject = this._getQueryObject(query);
+
+ queryObject.callbacksMatch.delete(uuid);
+ queryObject.callbacksUnmatch.delete(uuid);
+ queryObject.callbacksSetup.delete(uuid);
+ },
+
+ /**
+ * Returns a boolean value if a media query expression currently matches.
+ *
+ * @param {string=} query CSS media query
+ * @returns {boolean} true if query matches
+ */
+ is: function(query) {
+ var queryObject = this._getQueryObject(query);
+
+ if (query === 'large') {
+ // the query matches for max-width, we need to inverse the logic here
+ return !queryObject.mql.matches;
+ }
+
+ return queryObject.mql.matches;
+ },
+
+ /**
+ * Disables scrolling of body element.
+ */
+ scrollDisable: function() {
+ if (_scrollDisableCounter === 0) {
+ _bodyOverflow = document.body.style.getPropertyValue('overflow');
+
+ document.body.style.setProperty('overflow', 'hidden', '');
+ }
+
+ _scrollDisableCounter++;
+ },
+
+ /**
+ * Re-enables scrolling of body element.
+ */
+ scrollEnable: function() {
+ if (_scrollDisableCounter) {
+ _scrollDisableCounter--;
+
+ if (_scrollDisableCounter === 0) {
+ if (_bodyOverflow) {
+ document.body.style.setProperty('overflow', _bodyOverflow, '');
+ }
+ else {
+ document.body.style.removeProperty('overflow');
+ }
+ }
+ }
+ },
+
+ /**
+ *
+ * @param {string=} query CSS media query
+ * @return {Object} object containing callbacks and MediaQueryList
+ * @protected
+ */
+ _getQueryObject: function(query) {
+ if (typeof query !== 'string') query = '';
+ if (query === '' || query === 'small' || query === 'large') {
+ query = '(max-width: 767px)';
+ }
+
+ var queryObject = _mql.get(query);
+ if (!queryObject) {
+ queryObject = {
+ callbacksMatch: new Dictionary(),
+ callbacksUnmatch: new Dictionary(),
+ callbacksSetup: new Dictionary(),
+ mql: window.matchMedia(query)
+ };
+ queryObject.mql.addListener(this._mqlChange.bind(this));
+
+ _mql.set(query, queryObject);
+ }
+
+ return queryObject;
+ },
+
+ /**
+ * Triggered whenever a registered media query now matches or no longer matches.
+ *
+ * @param {Event} event event object
+ * @protected
+ */
+ _mqlChange: function(event) {
+ var queryObject = this._getQueryObject(event.media);
+ if (event.matches) {
+ if (queryObject.callbacksSetup.size) {
+ queryObject.callbacksSetup.forEach(function(callback) {
+ callback();
+ });
+
+ // discard all setup callbacks after execution
+ queryObject.callbacksSetup = new Dictionary();
+ }
+
+ queryObject.callbacksMatch.forEach(function(callback) {
+ callback();
+ });
+ }
+ else {
+ queryObject.callbacksUnmatch.forEach(function(callback) {
+ callback();
+ });
+ }
+ }
+ };
+});
--- /dev/null
+/*
+ * Polyfill for `Element.prototype.matches()` and `Element.prototype.closest()`
+ * Copyright (c) 2015 Jonathan Neal - https://github.com/jonathantneal/closest
+ * License: CC0 1.0 Universal (https://creativecommons.org/publicdomain/zero/1.0/)
+ */
+(function(ELEMENT) {
+ ELEMENT.matches = ELEMENT.matches || ELEMENT.mozMatchesSelector || ELEMENT.msMatchesSelector || ELEMENT.oMatchesSelector || ELEMENT.webkitMatchesSelector;
+
+ ELEMENT.closest = ELEMENT.closest || function closest(selector) {
+ var element = this;
+
+ while (element) {
+ if (element.matches(selector)) {
+ break;
+ }
+
+ element = element.parentElement;
+ }
+
+ return element;
+ };
+}(Element.prototype));
'Ui/Dialog': 'WoltLab/WCF/Ui/Dialog',
'Ui/Notification': 'WoltLab/WCF/Ui/Notification',
'Ui/ReusableDropdown': 'WoltLab/WCF/Ui/Dropdown/Reusable',
+ 'Ui/Screen': 'WoltLab/WCF/Ui/Screen',
'Ui/SimpleDropdown': 'WoltLab/WCF/Ui/Dropdown/Simple',
'Ui/TabMenu': 'WoltLab/WCF/Ui/TabMenu',
'Upload': 'WoltLab/WCF/Upload'
*
* @param {string} selector CSS selector
* @param {Element=} context target element, assuming `document` if omitted
+ * @param {function=} callback callback function pased to forEach()
* @return {NodeList} matching elements
*/
- window.elBySelAll = function(selector, context) {
- return (context || document).querySelectorAll(selector);
+ window.elBySelAll = function(selector, context, callback) {
+ var nodeList = (context || document).querySelectorAll(selector);
+ if (typeof callback === 'function') {
+ Array.prototype.forEach.call(nodeList, callback);
+ }
+
+ return nodeList;
};
/**
box-shadow: $parameters;
}
/** /deprecated */
+
+@mixin small-screen-only() {
+ /* 768px - 1px */
+ @media (max-width: 767px) {
+ @content;
+ }
+}
+
+@mixin large-screen-only() {
+ @media (min-width: 768px) {
+ @content;
+ }
+}
.layoutBoundary {
margin: 0 auto;
- padding: 0 20px;
- @if $useFluidLayout {
- min-width: $wcfLayoutMinWidth;
- max-width: $wcfLayoutMaxWidth;
+ @include small-screen-only {
+ padding: 0 10px;
+ width: 100%;
}
- @else {
- width: $wcfLayoutFixedWidth;
+
+ @include large-screen-only {
+ padding: 0 20px;
+
+ @if $useFluidLayout {
+ min-width: $wcfLayoutMinWidth;
+ max-width: $wcfLayoutMaxWidth;
+ } @else {
+ width: $wcfLayoutFixedWidth;
+ }
}
}
padding: 40px 0;
z-index: 50;
- > div {
- display: flex;
- }
-
a {
color: $wcfContentLink;
}
}
-.content {
- flex: 1 1 auto;
-
- // sidebar follows
- &:not(:last-child) {
- flex-basis: calc(100% - 340px);
- max-width: calc(100% - 340px); // IE fix
- }
-}
-
-.sidebar {
- flex: 0 0 310px;
-
- &:first-child {
- margin-right: 30px;
+/* use flex-box to enforce a proper side-by-side layout on desktop */
+@include large-screen-only {
+ .main > div {
+ display: flex;
}
- & + .content {
- flex-basis: calc(100% - 340px);
- max-width: calc(100% - 340px); // IE fix
+ .content {
+ flex: 1 1 auto;
// sidebar follows
&:not(:last-child) {
- flex-basis: calc(100% - 680px);
- max-width: calc(100% - 680px); // IE fix
+ flex-basis: calc(100% - 340px);
+ max-width: calc(100% - 340px); // IE fix
+ }
+
+ & + .sidebar {
+ margin-left: 30px;
+ }
+ }
+
+ .sidebar {
+ flex: 0 0 310px;
+
+ &:first-child {
+ margin-right: 30px;
+ }
+
+ & + .content {
+ flex-basis: calc(100% - 340px);
+ max-width: calc(100% - 340px); // IE fix
+
+ // sidebar follows
+ &:not(:last-child) {
+ flex-basis: calc(100% - 680px);
+ max-width: calc(100% - 680px); // IE fix
+ }
}
}
}
-.content + .sidebar {
- margin-left: 30px;
-}
}
/* LOGO */
-#logo {
+.pageHeaderLogo {
// use a fixed width of 50% together with the search bar to force a wrap
flex: 0 0 50%;
// gap between the two rows formed by the wrapping flex box
margin-bottom: 15px;
- > a > img.small {
+ .pageHeaderLogoSmall {
display: none;
}
}
}
}
}
+
+@include small-screen-only {
+ .pageHeader > div > div {
+ padding-bottom: 10px;
+ padding-top: 10px;
+ }
+
+ .pageHeaderLogo {
+ flex: 1 1 auto;
+ margin: 0 10px;
+ order: 2;
+ text-align: center;
+
+ .pageHeaderLogoLarge {
+ display: none;
+ }
+
+ .pageHeaderLogoSmall {
+ display: inline;
+ }
+ }
+
+ .userPanel {
+ flex: 0 0 auto;
+ order: 3;
+
+ &::before {
+ content: $fa-var-user;
+ }
+
+ > .userPanelItems {
+ display: none;
+ }
+ }
+
+ .mainMenu {
+ flex: 0 0 auto;
+ order: 1;
+
+ &::before {
+ content: $fa-var-bars;
+ }
+
+ > .boxContent {
+ display: none;
+ }
+ }
+
+ .mainMenu,
+ .userPanel {
+ &::before {
+ background-color: $wcfHeaderMenuBackground;
+ color: $wcfHeaderMenuLink;
+ font-family: FontAwesome;
+ font-size: 28px;
+ line-height: 32px;
+ padding: 5px 10px;
+ }
+
+ &:hover::before {
+ background-color: $wcfHeaderMenuBackgroundActive;
+ color: $wcfHeaderMenuLinkActive;
+ }
+ }
+
+ .pageHeaderSearch {
+ display: none;
+ }
+}
}
}
- #logo {
- flex: 0 auto;
- margin-bottom: 0;
- order: 1;
-
- > a > .large {
- display: none;
- }
-
- > a > .small {
- display: block;
- }
- }
-
- .mainMenu {
- flex: 1 auto;
- order: 2;
- margin: 0 20px;
- }
-
- .userPanel {
- flex: 0 auto;
- order: 3;
- margin-right: 20px;
- }
-
- .pageHeaderSearch {
- flex: 0 auto;
- order: 4;
- }
-
.pageHeaderSearchInputContainer:not(.open) {
> .pageHeaderSearchInput {
padding-right: 20px;
pointer-events: none;
}
}
+
+ @include large-screen-only {
+ #logo {
+ flex: 0 auto;
+ margin-bottom: 0;
+ order: 1;
+
+ > a > .large {
+ display: none;
+ }
+
+ > a > .small {
+ display: block;
+ }
+ }
+
+ .mainMenu {
+ flex: 1 auto;
+ order: 2;
+ margin: 0 20px;
+ }
+
+ .userPanel {
+ flex: 0 auto;
+ order: 3;
+ margin-right: 20px;
+ }
+
+ .pageHeaderSearch {
+ flex: 0 auto;
+ order: 4;
+ }
+ }
}
-.pageNavigation {
- background-color: $wcfNavigationBackground;
- color: $wcfNavigationText;
- flex: 0 0 auto;
- padding: 5px 0;
- z-index: 25;
-
- > div {
- align-items: center;
- display: flex;
- justify-content: flex-end;
- height: 30px;
- }
-
- .icon {
+@include large-screen-only {
+ .pageNavigation {
+ background-color: $wcfNavigationBackground;
color: $wcfNavigationText;
- }
-
- a {
- color: $wcfNavigationLink;
+ flex: 0 0 auto;
+ padding: 5px 0;
+ z-index: 25;
+
+ > div {
+ align-items: center;
+ display: flex;
+ justify-content: flex-end;
+ height: 30px;
+ }
+
+ .icon {
+ color: $wcfNavigationText;
+ }
- &:hover {
- color: $wcfNavigationLinkActive;
+ a {
+ color: $wcfNavigationLink;
+
+ &:hover {
+ color: $wcfNavigationLinkActive;
+ }
}
}
-}
-
-.pageNavigationIcons {
- display: flex;
- flex: 0 0 auto;
- flex-direction: row-reverse;
- > li {
+ .pageNavigationIcons {
+ display: flex;
flex: 0 0 auto;
+ flex-direction: row-reverse;
- &:not(:last-child) {
- margin-left: 10px;
- }
-
- > a {
- > .icon {
- color: $wcfHeaderLink;
+ > li {
+ flex: 0 0 auto;
+
+ &:not(:last-child) {
+ margin-left: 10px;
}
- &:hover > .icon {
- color: $wcfHeaderLinkActive;
+ > a {
+ > .icon {
+ color: $wcfHeaderLink;
+ }
+
+ &:hover > .icon {
+ color: $wcfHeaderLinkActive;
+ }
}
}
}
}
+
+@include small-screen-only {
+ .pageNavigation {
+ display: none;
+ }
+}
}
}
-@media only screen and (max-width: 800px) {
-}
-
/* static dialogs */
.jsStaticDialogContent {
display: none;
min-height: 20px;
}
-@media only screen and (max-width: 800px) {
+@include small-screen-only {
.dropdownMenu {
left: 0 !important;
right: 0 !important;
overflow: visible;
max-height: none;
}
+
+ @include small-screen-only {
+ > .elementPointer {
+ display: none;
+ }
+ }
}
/* drop down header */
flex: 0 0 auto;
margin-left: 5px;
}
+
+ @include small-screen-only {
+ padding: 10px;
+ }
}
/* container for dropdown items */
.interactiveDropdownItemsContainer {
border: 1px solid $wcfContentBorderInner;
border-width: 1px 0;
- max-height: 300px;
&.ps-container {
> .interactiveDropdownItems {
align-items: center;
overflow: hidden;
}
+
+ @include small-screen-only {
+ padding: 10px;
+ }
}
.loading,
}
}
-@media only screen and (min-width: 801px) {
+@include large-screen-only {
.interactiveDropdown {
min-width: 350px;
}
.interactiveDropdownItemsContainer {
+ max-height: 400px;
overflow: hidden;
position: relative;
}
}
}
-/* todo: mobile version
-@media only screen and (max-width: 800px) {
- .DEBUG_ONLY_interactiveDropdown {
- border-width: 1px 0;
- box-sizing: border-box;
- left: 0 !important;
- right: 0 !important;
- width: 100%;
+@include small-screen-only {
+ .interactiveDropdown {
+ bottom: 0;
+ display: flex;
+ flex-direction: column;
+ left: 0;
+ position: fixed;
+ right: 0;
+ }
+
+ .interactiveDropdownHeader {
+ flex: 0 0 auto;
+ }
+
+ .interactiveDropdownItemsContainer {
+ flex: 1 1 auto;
+ overflow: auto;
- > .interactiveDropdownItemsContainer {
- overflow-x: auto;
+ /* increase the clickable area of the mark as read icon */
+ .interactiveDropdownItemMarkAsRead {
+ bottom: 0;
+ position: absolute;
+ right: 0;
+ top: 0;
+ width: 36px; /* 16px icon + 2x 10px padding */
- > .interactiveDropdownItems > li.interactiveDropdownItemOutstandingIcon > div.interactiveDropdownItemMarkAsRead {
- bottom: 0;
- position: absolute;
- right: 0;
- top: 0;
- //width: (@wcfGapSmall + @wcfGapTiny) + 16px + (@wcfGapSmall + @wcfGapTiny);
+ > a {
+ display: block;
+ height: 100%;
+ text-align: center;
- > a {
- display: block;
- height: 100%;
- text-align: center;
+ > .icon {
+ position: relative;
+ top: 50%;
- > .icon {
- position: relative;
- top: 50%;
-
- transform: translateY(-50%);
- -ms-transform: translateY(-50%);
- -webkit-transform: translateY(-50%);
- }
+ transform: translateY(-50%);
}
}
}
}
+
+ .interactiveDropdownShowAll {
+ flex: 0 0 auto;
+ }
}
-*/
\ No newline at end of file
--- /dev/null
+/* animations for overlay appearing from the left */
+@keyframes wcfMenuOverlayLeft {
+ 0% { visibility: hidden; transform: translateX(-100%); }
+ 100% { visibility: visible; transform: translateX(0); }
+}
+
+@keyframes wcfMenuOverlayLeftOut {
+ 0% { visibility: visible; transform: translateX(0); }
+ 99% { visibility: visible; transform: translateX(-100%); }
+ 100% { visibility: hidden; transform: translateX(-100%); }
+}
+
+/* animations for overlay appearing from the right */
+@keyframes wcfMenuOverlayRight {
+ 0% { visibility: hidden; transform: translateX(100%); }
+ 100% { visibility: visible; transform: translateX(0); }
+}
+
+@keyframes wcfMenuOverlayRightOut {
+ 0% { visibility: visible; transform: translateX(0); }
+ 99% { visibility: visible; transform: translateX(100%); }
+ 100% { visibility: hidden; transform: translateX(100%); }
+}
+
+/* menu container */
+.menuOverlayMobile {
+ bottom: 0;
+ left: 0;
+ overflow: hidden;
+ position: fixed;
+ right: 0;
+ top: 0;
+ visibility: hidden;
+ z-index: 320;
+
+ &.enableAnimation {
+ animation: wcfMenuOverlayLeftOut .3s;
+ animation-fill-mode: forwards;
+
+ /* different animation for user menu */
+ &.pageUserMenuMobile {
+ animation-name: wcfMenuOverlayRightOut;
+ }
+ }
+
+ &.open {
+ animation: wcfMenuOverlayLeft .3s;
+ animation-fill-mode: forwards;
+
+ /* different animation for user menu */
+ &.pageUserMenuMobile {
+ animation-name: wcfMenuOverlayRight;
+ }
+ }
+
+ /* work-around to avoid setting explicit visibility */
+ > .menuOverlayItemList:not(.hidden) {
+ visibility: inherit;
+ }
+}
+
+.menuOverlayItemWrapper {
+ display: flex;
+ justify-content: flex-end;
+
+ > .menuOverlayItemLink {
+ flex: 1 1 auto;
+ }
+}
+
+.menuOverlayItemList {
+ background-color: rgb(44, 62, 80);
+ box-shadow: -5px 0 10px 0 rgba(0, 0, 0, .2);
+ list-style-type: none;
+ margin: 0;
+ padding: 10px 0;
+ position: fixed;
+ top: 0;
+ left: -1px;
+ right: 0;
+ bottom: 0;
+ width: calc(100% + 1px);
+ z-index: 450;
+ transition: margin-left .3s cubic-bezier(.25, .46, .45, .94);
+ transition-timing-function: linear;
+ will-change: margin-left;
+
+ /* chaining `.hidden` and `.active` below is required because each active
+ item list receives `.active` but it could still be `.hidden` due to
+ a child list being active */
+ &.hidden,
+ &.hidden.active {
+ margin-left: -25%;
+ }
+}
+
+.menuOverlayItemSpacer {
+ margin-top: 20px;
+
+ /* avoid successive spacers piling up */
+ & + .menuOverlayItemSpacer {
+ display: none;
+ }
+}
+
+.menuOverlayItem {
+ &:not(:last-child) {
+ margin-bottom: 1px
+ }
+
+ /* nested item list */
+ > .menuOverlayItemList {
+ margin-left: 110%;
+ z-index: 500;
+
+ &.active {
+ margin-left: 0;
+ overflow: scroll;
+ }
+ }
+}
+
+.menuOverlayItemLink,
+.menuOverlayTitle,
+.menuOverlayBackLink {
+ color: rgb(255, 255, 255);
+ display: block;
+ font-size: 14px;
+ padding: 10px 30px;
+ position: relative;
+}
+
+.menuOverlayItemLink {
+ background-color: rgb(52, 73, 94);
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+
+ .icon::before {
+ color: #fff;
+ }
+
+ /* wrapper class for links containing an additional badge */
+ &.menuOverlayItemBadge {
+ align-items: center;
+ display: flex;
+ padding-right: 10px;
+
+ /* different padding if there is no additional icon after the link,
+ ensures proper alignment for links with badges containing a child
+ item list */
+ &:last-child {
+ /* 55px = 10px padding + 1px margin + icon */
+ /* icon = 2x 10px padding + 16px width */
+ padding-right: 55px;
+ }
+
+ > .menuOverlayItemTitle {
+ flex: 1 1 auto;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ }
+
+ > .badge {
+ flex: 0 0 auto;
+ }
+ }
+
+ &.menuOverlayItemLinkMore::after {
+ color: rgb(204, 204, 204);
+ content: $fa-var-angle-right;
+ display: block;
+ font-family: FontAwesome;
+ font-size: 24px;
+ position: absolute;
+ right: 10px;
+ top: 50%;
+ transform: translateY(-50%);
+ }
+}
+
+.menuOverlayTitle {
+ color: rgb(204, 204, 204);
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+
+ &:not(:first-child) {
+ margin-top: 10px;
+ }
+}
+
+/* icon link sharing the space with a link or (header only) the logo */
+.menuOverlayItemLinkIcon {
+ background-color: rgb(52, 73, 94);
+ flex: 0 0 auto;
+ margin-left: 1px;
+ padding: 10px;
+
+ /* force explicit dimensions because no each .icon24 is of equal height/width */
+ height: 44px;
+ width: 44px;
+
+ > .icon::before {
+ color: #fff;
+ }
+}
+
+.menuOverlayBackLink::before {
+ color: rgb(204, 204, 204);
+ content: $fa-var-angle-left;
+ display: block;
+ font-family: FontAwesome;
+ font-size: 18px;
+ position: absolute;
+ left: 10px;
+ top: 50%;
+ transform: translateY(-50%);
+}
+
+.menuOverlayLogoWrapper {
+ flex: 1 1 auto;
+ padding: 5px;
+ display: flex;
+
+ .menuOverlayLogo {
+ flex: 1 1 auto;
+ background-size: contain;
+ background-repeat: no-repeat;
+ background-position: center;
+ }
+}
}
/* disable auto zoom in mobile safari */
-@media only screen and (max-width: 800px) {
+@include small-screen-only {
.redactor-editor + textarea {
font-size: 16px;
max-height: 500px;
}
}
-@media only screen and (max-width: 800px) {
+@include small-screen-only {
.messageTabMenu {
> nav > ul > li:not(.active) > a {
> span.icon {