<template>pageLanding</template>
<template>pageMenuItemAdd</template>
<template>pageMenuItemList</template>
+ <template>pageMenuMobile</template>
<template>reactionTypeIcon</template>
<template>setupFooter</template>
<template>setupHeader</template>
<file>acp/templates/pageLogo.tpl</file>
<file>acp/templates/pageMenuItemAdd.tpl</file>
<file>acp/templates/pageMenuItemList.tpl</file>
+ <file>acp/templates/pageMenuMobile.tpl</file>
<file>acp/templates/pageMenuUser.tpl</file>
<file>acp/templates/pageSearchArea.tpl</file>
<file>acp/templates/reactionTypeIcon.tpl</file>
<file>js/WoltLabSuite/Core/Media/Search.js</file>
<file>js/WoltLabSuite/Core/Ui/InlineEditor.js</file>
<file>js/WoltLabSuite/Core/Ui/Page/JumpToTop.js</file>
+ <file>js/WoltLabSuite/Core/Ui/Page/Menu/Abstract.js</file>
<file>js/WoltLabSuite/Core/Ui/Reaction/ReputationButtons.js</file>
<file>js/WoltLabSuite/Core/Ui/Redactor/RuntimeStyle.js</file>
<file>js/closest.js</file>
<file>style/ui/dialog.less</file>
<file>style/ui/dropdown.less</file>
<file>style/ui/dropdownInteractive.less</file>
+ <file>style/ui/menuMobile.scss</file>
<file>style/ui/message.less</file>
<file>style/ui/poll.less</file>
<file>style/ui/popover.less</file>
<template>multifactorManageEmail</template>
<template>notificationListOustanding</template>
<template>pageLogo</template>
+ <template>pageMenuMobile</template>
<template>pageNavbarBottom</template>
<template>privacyPolicy</template>
<template>quoteBBCodeTag</template>
{include file='pageFooter'}
</div>
-{include file='pageMenuMobile'}
-
{event name='footer'}
<div class="pageFooterStickyNotice">
+++ /dev/null
-{* main menu / page options / breadcrumbs *}
-<div id="pageMainMenuMobile" class="pageMainMenuMobile menuOverlayMobile" data-page-logo="{$__wcf->getStyleHandler()->getStyle()->getPageLogo()}">
- <ol class="menuOverlayItemList" data-title="{lang}wcf.menu.page{/lang}">
- {event name='menuBefore'}
-
- <li class="menuOverlayTitle">{lang}wcf.menu.page.navigation{/lang}</li>
- {foreach from=$__wcf->getBoxHandler()->getBoxByIdentifier('com.woltlab.wcf.MainMenu')->getMenu()->getMenuItemNodeList() item=menuItemNode}
- {* Does not use `data-identifier` to prevent compatibility issues. See https://github.com/WoltLab/WCF/pull/2813 *}
- <li class="menuOverlayItem" data-mobile-identifier="{@$menuItemNode->identifier}">
- {assign var=__outstandingItems value=$menuItemNode->getOutstandingItems()}
- <a href="{$menuItemNode->getURL()}" class="menuOverlayItemLink{if $__outstandingItems} menuOverlayItemBadge{/if}{if $menuItemNode->isActiveNode()} active{/if}"{if $menuItemNode->isExternalLink() && EXTERNAL_LINK_TARGET_BLANK} target="_blank"{/if}>
- <span class="menuOverlayItemTitle">{$menuItemNode->getTitle()}</span>
- {if $__outstandingItems}
- <span class="badge badgeUpdate">{#$__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}
-
- {if $__wcf->getBoxHandler()->getBoxByIdentifier('com.woltlab.wcf.FooterMenu')}
- {hascontent}
- <li class="menuOverlayItemSpacer"></li>
- {content}
- {foreach from=$__wcf->getBoxHandler()->getBoxByIdentifier('com.woltlab.wcf.FooterMenu')->getMenu()->getMenuItemNodeList() item=menuItemNode}
- {* Does not use `data-identifier` to prevent compatibility issues. See https://github.com/WoltLab/WCF/pull/2813 *}
- <li class="menuOverlayItem" data-mobile-identifier="{@$menuItemNode->identifier}">
- {assign var=__outstandingItems value=$menuItemNode->getOutstandingItems()}
- <a href="{$menuItemNode->getURL()}" class="menuOverlayItemLink{if $__outstandingItems} menuOverlayItemBadge{/if}{if $menuItemNode->isActiveNode()} active{/if}"{if $menuItemNode->isExternalLink() && EXTERNAL_LINK_TARGET_BLANK} target="_blank"{/if}>
- <span class="menuOverlayItemTitle">{$menuItemNode->getTitle()}</span>
- {if $__outstandingItems}
- <span class="badge badgeUpdate">{#$__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}
- {/content}
- {/hascontent}
- {/if}
-
- <li class="menuOverlayItemSpacer"></li>
- <li class="menuOverlayItem" data-more="com.woltlab.wcf.search">
- <a href="#" class="menuOverlayItemLink box24">
- <span class="icon icon24 fa-search"></span>
- <span class="menuOverlayItemTitle">{lang}wcf.global.search{/lang}</span>
- </a>
- </li>
- <li class="menuOverlayTitle" id="pageMainMenuMobilePageOptionsTitle">{lang}wcf.menu.page.options{/lang}</li>
-
- {event name='menuItems'}
-
- {hascontent}
- <li class="menuOverlayTitle">{lang}wcf.menu.page.location{/lang}</li>
- {content}
- {assign var=__breadcrumbsDepth value=0}
- {foreach from=$__wcf->getBreadcrumbs() item=$breadcrumb}
- {* skip breadcrumbs that do not expose a visible label *}
- {if $breadcrumb->getLabel()}
- <li class="menuOverlayItem">
- <a href="{$breadcrumb->getURL()}" class="menuOverlayItemLink">
- <span{if $__breadcrumbsDepth} style="padding-left: {$__breadcrumbsDepth * 20}px" {/if} class="box24">
- <span class="icon icon24 fa-{if $__breadcrumbsDepth}caret-right{else}home{/if}"></span>
- <span class="menuOverlayItemTitle">{$breadcrumb->getLabel()}</span>
- </span>
- </a>
- </li>
- {assign var=__breadcrumbsDepth value=$__breadcrumbsDepth + 1}
- {/if}
- {/foreach}
- {/content}
- {/hascontent}
-
- {event name='menuAfter'}
- </ol>
-</div>
-
-{* user menu *}
-<div id="pageUserMenuMobile" class="pageUserMenuMobile menuOverlayMobile" data-page-logo="{$__wcf->getStyleHandler()->getStyle()->getPageLogo()}">
- <ol class="menuOverlayItemList" data-title="{lang}wcf.menu.user{/lang}">
- {event name='userMenuBefore'}
-
- {if $__wcf->user->userID}
- {* logged-in *}
- <li class="menuOverlayTitle">{lang}wcf.menu.user{/lang}</li>
- <li class="menuOverlayItem">
- <a href="{$__wcf->user->getLink()}" 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">{lang}wcf.user.panel.settings{/lang}</span>
- </a>
- <ol class="menuOverlayItemList">
- {event name='userMenuItemsBefore'}
-
- {foreach from=$__wcf->getUserMenu()->getMenuItems('') item=menuCategory}
- <li class="menuOverlayTitle">{$menuCategory->getTitle()}</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}
-
- {event name='userMenuItemsAfter'}
- </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 menuOverlayItemBadge box24" data-badge-identifier="userNotifications">
- <span class="icon icon24 fa-bell-o"></span>
- <span class="menuOverlayItemTitle">{lang}wcf.user.notification.notifications{/lang}</span>
- {if $__wcf->getUserNotificationHandler()->getNotificationCount()}<span class="badge badgeUpdate">{#$__wcf->getUserNotificationHandler()->getNotificationCount()}</span>{/if}
- </a>
- </li>
- {if $__wcf->user->userID && $__wcf->session->getPermission('mod.general.canUseModeration')}
- <li class="menuOverlayItem" data-more="com.woltlab.wcf.moderation">
- <a href="#" class="menuOverlayItemLink menuOverlayItemBadge box24" data-badge-identifier="outstandingModeration">
- <span class="icon icon24 fa-exclamation-triangle"></span>
- <span class="menuOverlayItemTitle">{lang}wcf.moderation.moderation{/lang}</span>
- {if $__wcf->getModerationQueueManager()->getUnreadModerationCount()}<span class="badge badgeUpdate">{#$__wcf->getModerationQueueManager()->getUnreadModerationCount()}</span>{/if}
- </a>
- </li>
- {/if}
-
- {event name='userMenuItems'}
-
- <li class="menuOverlayItemSpacer"></li>
- <li class="menuOverlayItem">
- <a href="{link controller='Logout'}t={csrfToken type=url}{/link}" class="menuOverlayItemLink box24">
- <span class="icon icon24 fa-sign-out"></span>
- <span class="menuOverlayItemTitle">{lang}wcf.user.logout{/lang}</span>
- </a>
- </li>
- {else}
- {* guest *}
- <li class="menuOverlayTitle">{lang}wcf.menu.user{/lang}</li>
- {if !$__disableLoginLink|isset}
- <li class="menuOverlayItem" data-more="com.woltlab.wcf.login">
- <a href="#" class="menuOverlayItemLink box24">
- <span class="icon icon24 fa-sign-in"></span>
- <span class="menuOverlayItemTitle">{lang}wcf.user.loginOrRegister{/lang}</span>
- </a>
- </li>
- {/if}
-
- {event name='guestUserMenuItems'}
-
- {if $__wcf->getLanguage()->getLanguages()|count > 1}
- <li class="menuOverlayItemSpacer"></li>
- <li class="menuOverlayTitle">{lang}wcf.user.language{/lang}</li>
- <li class="menuOverlayItem">
- <a href="#" class="menuOverlayItemLink box24">
- <img src="{$__wcf->getLanguage()->getIconPath()}" alt="">
- <span class="menuOverlayItemTitle">{$__wcf->getLanguage()}</span>
- </a>
- <ol class="menuOverlayItemList" data-title="{lang}wcf.user.language{/lang}">
- {foreach from=$__wcf->getLanguage()->getLanguages() item=_language}
- <li class="menuOverlayItem" data-more="com.woltlab.wcf.language" data-language-code="{$_language->getFixedLanguageCode()}" data-language-id="{@$_language->languageID}">
- <a href="#" class="menuOverlayItemLink box24">
- <img src="{$_language->getIconPath()}" alt="">
- <span class="menuOverlayItemTitle">{$_language}</span>
- </a>
- </li>
- {/foreach}
- </ol>
- </li>
- {/if}
- {/if}
-
- {event name='userMenuAfter'}
- </ol>
-</div>
+++ /dev/null
-/**
- * Provides a touch-friendly fullscreen menu.
- *
- * @author Alexander Ebert
- * @copyright 2001-2019 WoltLab GmbH
- * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module WoltLabSuite/Core/Ui/Page/Menu/Abstract
- */
-
-import * as Core from "../../../Core";
-import * as Environment from "../../../Environment";
-import * as EventHandler from "../../../Event/Handler";
-import * as Language from "../../../Language";
-import * as DomTraverse from "../../../Dom/Traverse";
-import * as UiScreen from "../../Screen";
-
-const _pageContainer = document.getElementById("pageContainer")!;
-
-const enum TouchPosition {
- AtEdge = 20,
- MovedHorizontally = 5,
- MovedVertically = 20,
-}
-
-/**
- * Which edge of the menu is touched? Empty string
- * if no menu is currently touched.
- *
- * One 'left', 'right' or ''.
- */
-let _androidTouching = "";
-
-interface ItemData {
- itemList: HTMLOListElement;
- parentItemList: HTMLOListElement;
-}
-
-abstract class UiPageMenuAbstract {
- private readonly activeList: HTMLOListElement[] = [];
- protected readonly button: HTMLElement;
- private depth = 0;
- private enabled = true;
- private readonly eventIdentifier: string;
- private readonly items = new Map<HTMLAnchorElement, ItemData>();
- protected readonly menu: HTMLElement;
- private removeActiveList = false;
-
- protected constructor(eventIdentifier: string, elementId: string, buttonSelector: string) {
- if (document.body.dataset.template === "packageInstallationSetup") {
- // work-around for WCFSetup on mobile
- return;
- }
-
- this.eventIdentifier = eventIdentifier;
- this.menu = document.getElementById(elementId)!;
-
- const callbackOpen = this.open.bind(this);
- this.button = document.querySelector(buttonSelector) as HTMLElement;
- this.button.addEventListener("click", callbackOpen);
-
- this.initItems();
- this.initHeader();
-
- EventHandler.add(this.eventIdentifier, "open", callbackOpen);
- EventHandler.add(this.eventIdentifier, "close", this.close.bind(this));
- EventHandler.add(this.eventIdentifier, "updateButtonState", this.updateButtonState.bind(this));
-
- this.menu.addEventListener("animationend", () => {
- if (!this.menu.classList.contains("open")) {
- this.menu.querySelectorAll(".menuOverlayItemList").forEach((itemList) => {
- // force the main list to be displayed
- itemList.classList.remove("active", "hidden");
- });
- }
- });
-
- this.menu.children[0].addEventListener("transitionend", () => {
- this.menu.classList.add("allowScroll");
-
- if (this.removeActiveList) {
- this.removeActiveList = false;
-
- const list = this.activeList.pop();
- if (list) {
- list.classList.remove("activeList");
- }
- }
- });
-
- const backdrop = document.createElement("div");
- backdrop.className = "menuOverlayMobileBackdrop";
- backdrop.addEventListener("click", this.close.bind(this));
-
- this.menu.insertAdjacentElement("afterend", backdrop);
-
- this.menu.parentElement!.insertBefore(backdrop, this.menu.nextSibling);
-
- this.updateButtonState();
-
- if (Environment.platform() === "android") {
- this.initializeAndroid();
- }
- }
-
- /**
- * Opens the menu.
- */
- open(event?: MouseEvent): boolean {
- if (!this.enabled) {
- return false;
- }
-
- if (event instanceof Event) {
- event.preventDefault();
- }
-
- this.menu.classList.add("open");
- this.menu.classList.add("allowScroll");
- this.menu.children[0].classList.add("activeList");
-
- UiScreen.scrollDisable();
-
- _pageContainer.classList.add("menuOverlay-" + this.menu.id);
-
- UiScreen.pageOverlayOpen();
-
- return true;
- }
-
- /**
- * Closes the menu.
- */
- close(event?: Event): boolean {
- if (event instanceof Event) {
- event.preventDefault();
- }
-
- if (this.menu.classList.contains("open")) {
- this.menu.classList.remove("open");
-
- UiScreen.scrollEnable();
- UiScreen.pageOverlayClose();
-
- _pageContainer.classList.remove("menuOverlay-" + this.menu.id);
-
- return true;
- }
-
- return false;
- }
-
- /**
- * Enables the touch menu.
- */
- enable(): void {
- this.enabled = true;
- }
-
- /**
- * Disables the touch menu.
- */
- disable(): void {
- this.enabled = false;
-
- this.close();
- }
-
- /**
- * Initializes the Android Touch Menu.
- */
- private initializeAndroid(): void {
- // specify on which side of the page the menu appears
- let appearsAt: "left" | "right";
- switch (this.menu.id) {
- case "pageUserMenuMobile":
- appearsAt = "right";
- break;
- case "pageMainMenuMobile":
- appearsAt = "left";
- break;
- default:
- return;
- }
-
- const backdrop = this.menu.nextElementSibling as HTMLElement;
-
- // horizontal position of the touch start
- let touchStart: { x: number; y: number } | undefined = undefined;
-
- document.addEventListener("touchstart", (event) => {
- const touches = event.touches;
-
- let isLeftEdge: boolean;
- let isRightEdge: boolean;
-
- const isOpen = this.menu.classList.contains("open");
-
- // check whether we touch the edges of the menu
- if (appearsAt === "left") {
- isLeftEdge = !isOpen && touches[0].clientX < TouchPosition.AtEdge;
- isRightEdge = isOpen && Math.abs(this.menu.offsetWidth - touches[0].clientX) < TouchPosition.AtEdge;
- } else {
- isLeftEdge =
- isOpen &&
- Math.abs(document.body.clientWidth - this.menu.offsetWidth - touches[0].clientX) < TouchPosition.AtEdge;
- isRightEdge = !isOpen && document.body.clientWidth - touches[0].clientX < TouchPosition.AtEdge;
- }
-
- // abort if more than one touch
- if (touches.length > 1) {
- if (_androidTouching) {
- Core.triggerEvent(document, "touchend");
- }
- return;
- }
-
- // break if a touch is in progress
- if (_androidTouching) {
- return;
- }
-
- // break if no edge has been touched
- if (!isLeftEdge && !isRightEdge) {
- return;
- }
-
- // break if a different menu is open
- if (UiScreen.pageOverlayIsActive()) {
- const found = _pageContainer.classList.contains(`menuOverlay-${this.menu.id}`);
- if (!found) {
- return;
- }
- }
- // break if redactor is in use
- if (document.documentElement.classList.contains("redactorActive")) {
- return;
- }
-
- touchStart = {
- x: touches[0].clientX,
- y: touches[0].clientY,
- };
-
- if (isLeftEdge) {
- _androidTouching = "left";
- }
- if (isRightEdge) {
- _androidTouching = "right";
- }
- });
-
- document.addEventListener("touchend", (event) => {
- // break if we did not start a touch
- if (!_androidTouching || !touchStart) {
- return;
- }
-
- // break if the menu did not even start opening
- if (!this.menu.classList.contains("open")) {
- // reset
- touchStart = undefined;
- _androidTouching = "";
- return;
- }
-
- // last known position of the finger
- let position: number;
- if (event) {
- position = event.changedTouches[0].clientX;
- } else {
- position = touchStart.x;
- }
-
- // clean up touch styles
- this.menu.classList.add("androidMenuTouchEnd");
- this.menu.style.removeProperty("transform");
- backdrop.style.removeProperty(appearsAt);
- this.menu.addEventListener(
- "transitionend",
- () => {
- this.menu.classList.remove("androidMenuTouchEnd");
- },
- { once: true },
- );
-
- // check whether the user moved the finger far enough
- if (appearsAt === "left") {
- if (_androidTouching === "left" && position < touchStart.x + 100) {
- this.close();
- }
- if (_androidTouching === "right" && position < touchStart.x - 100) {
- this.close();
- }
- } else {
- if (_androidTouching === "left" && position > touchStart.x + 100) {
- this.close();
- }
- if (_androidTouching === "right" && position > touchStart.x - 100) {
- this.close();
- }
- }
-
- // reset
- touchStart = undefined;
- _androidTouching = "";
- });
-
- document.addEventListener("touchmove", (event) => {
- // break if we did not start a touch
- if (!_androidTouching || !touchStart) {
- return;
- }
-
- const touches = event.touches;
-
- // check whether the user started moving in the correct direction
- // this avoids false positives, in case the user just wanted to tap
- let movedFromEdge = false;
- if (_androidTouching === "left") {
- movedFromEdge = touches[0].clientX > touchStart.x + TouchPosition.MovedHorizontally;
- }
- if (_androidTouching === "right") {
- movedFromEdge = touches[0].clientX < touchStart.x - TouchPosition.MovedHorizontally;
- }
-
- const movedVertically = Math.abs(touches[0].clientY - touchStart.y) > TouchPosition.MovedVertically;
-
- let isOpen = this.menu.classList.contains("open");
- if (!isOpen && movedFromEdge && !movedVertically) {
- // the menu is not yet open, but the user moved into the right direction
- this.open();
- isOpen = true;
- }
-
- if (isOpen) {
- // update CSS to the new finger position
- let position = touches[0].clientX;
- if (appearsAt === "right") {
- position = document.body.clientWidth - position;
- }
- if (position > this.menu.offsetWidth) {
- position = this.menu.offsetWidth;
- }
- if (position < 0) {
- position = 0;
- }
-
- const offset = (appearsAt === "left" ? 1 : -1) * (position - this.menu.offsetWidth);
- this.menu.style.setProperty("transform", `translateX(${offset}px)`);
- backdrop.style.setProperty(appearsAt, Math.min(this.menu.offsetWidth, position).toString() + "px");
- }
- });
- }
-
- /**
- * Initializes all menu items.
- */
- private initItems(): void {
- this.menu.querySelectorAll(".menuOverlayItemLink").forEach((element: HTMLAnchorElement) => {
- this.initItem(element);
- });
- }
-
- /**
- * Initializes a single menu item.
- */
- private initItem(item: HTMLAnchorElement): void {
- // check if it should contain a 'more' link w/ an external callback
- const parent = item.parentElement!;
- const more = parent.dataset.more;
- if (more) {
- item.addEventListener("click", (event) => {
- event.preventDefault();
- event.stopPropagation();
-
- EventHandler.fire(this.eventIdentifier, "more", {
- handler: this,
- identifier: more,
- item: item,
- parent: parent,
- });
- });
-
- return;
- }
-
- const itemList = item.nextElementSibling as HTMLOListElement;
- if (itemList === null) {
- return;
- }
-
- // handle static items with an icon-type button next to it (acp menu)
- if (itemList.nodeName !== "OL" && itemList.classList.contains("menuOverlayItemLinkIcon")) {
- // add wrapper
- const wrapper = document.createElement("span");
- wrapper.className = "menuOverlayItemWrapper";
- parent.insertBefore(wrapper, item);
- wrapper.appendChild(item);
-
- while (wrapper.nextElementSibling) {
- wrapper.appendChild(wrapper.nextElementSibling);
- }
-
- return;
- }
-
- const isLink = item.getAttribute("href") !== "#";
- const parentItemList = parent.parentElement as HTMLOListElement;
- let itemTitle = itemList.dataset.title;
-
- this.items.set(item, {
- itemList: itemList,
- parentItemList: parentItemList,
- });
-
- if (!itemTitle) {
- itemTitle = DomTraverse.childByClass(item, "menuOverlayItemTitle")!.textContent!;
- itemList.dataset.title = itemTitle;
- }
-
- const callbackLink = this.showItemList.bind(this, item);
- if (isLink) {
- const wrapper = document.createElement("span");
- wrapper.className = "menuOverlayItemWrapper";
- parent.insertBefore(wrapper, item);
- wrapper.appendChild(item);
-
- const moreLink = document.createElement("a");
- moreLink.href = "#";
- moreLink.className = "menuOverlayItemLinkIcon" + (item.classList.contains("active") ? " active" : "");
- moreLink.innerHTML = '<span class="icon icon24 fa-angle-right"></span>';
- moreLink.addEventListener("click", callbackLink);
- wrapper.appendChild(moreLink);
- } else {
- item.classList.add("menuOverlayItemLinkMore");
- item.addEventListener("click", callbackLink);
- }
-
- const backLinkItem = document.createElement("li");
- backLinkItem.className = "menuOverlayHeader";
-
- const wrapper = document.createElement("span");
- wrapper.className = "menuOverlayItemWrapper";
-
- const backLink = document.createElement("a");
- backLink.href = "#";
- backLink.className = "menuOverlayItemLink menuOverlayBackLink";
- backLink.textContent = parentItemList.dataset.title || "";
- backLink.addEventListener("click", this.hideItemList.bind(this, item));
-
- const closeLink = document.createElement("a");
- closeLink.href = "#";
- closeLink.className = "menuOverlayItemLinkIcon";
- closeLink.innerHTML = '<span class="icon icon24 fa-times"></span>';
- closeLink.addEventListener("click", this.close.bind(this));
-
- wrapper.appendChild(backLink);
- wrapper.appendChild(closeLink);
- backLinkItem.appendChild(wrapper);
-
- itemList.insertBefore(backLinkItem, itemList.firstElementChild);
-
- if (!backLinkItem.nextElementSibling!.classList.contains("menuOverlayTitle")) {
- const titleItem = document.createElement("li");
- titleItem.className = "menuOverlayTitle";
- const title = document.createElement("span");
- title.textContent = itemTitle;
- titleItem.appendChild(title);
-
- itemList.insertBefore(titleItem, backLinkItem.nextElementSibling);
- }
- }
-
- /**
- * Renders the menu item list header.
- */
- private initHeader(): void {
- const listItem = document.createElement("li");
- listItem.className = "menuOverlayHeader";
-
- const wrapper = document.createElement("span");
- wrapper.className = "menuOverlayItemWrapper";
- listItem.appendChild(wrapper);
-
- const logoWrapper = document.createElement("span");
- logoWrapper.className = "menuOverlayLogoWrapper";
- wrapper.appendChild(logoWrapper);
-
- const logo = document.createElement("span");
- logo.className = "menuOverlayLogo";
- const pageLogo = this.menu.dataset.pageLogo!;
- logo.style.setProperty("background-image", `url("${pageLogo}")`, "");
- logoWrapper.appendChild(logo);
-
- const closeLink = document.createElement("a");
- closeLink.href = "#";
- closeLink.className = "menuOverlayItemLinkIcon";
- closeLink.innerHTML = '<span class="icon icon24 fa-times"></span>';
- closeLink.addEventListener("click", this.close.bind(this));
- wrapper.appendChild(closeLink);
-
- const list = DomTraverse.childByClass(this.menu, "menuOverlayItemList")!;
- list.insertBefore(listItem, list.firstElementChild);
- }
-
- /**
- * Hides an item list, return to the parent item list.
- */
- private hideItemList(item: HTMLAnchorElement, event: MouseEvent): void {
- if (event instanceof Event) {
- event.preventDefault();
- }
-
- this.menu.classList.remove("allowScroll");
- this.removeActiveList = true;
-
- const data = this.items.get(item)!;
- data.parentItemList.classList.remove("hidden");
-
- this.updateDepth(false);
- }
-
- /**
- * Shows the child item list.
- */
- private showItemList(item: HTMLAnchorElement, event: MouseEvent): void {
- event.preventDefault();
-
- const data = this.items.get(item)!;
-
- const load = data.itemList.dataset.load;
- if (load) {
- if (!Core.stringToBool(item.dataset.loaded || "")) {
- const target = event.currentTarget as HTMLElement;
- const icon = target.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;
- }
- }
-
- this.menu.classList.remove("allowScroll");
-
- data.itemList.classList.add("activeList");
- data.parentItemList.classList.add("hidden");
-
- this.activeList.push(data.itemList);
-
- this.updateDepth(true);
- }
-
- private updateDepth(increase: boolean): void {
- this.depth += increase ? 1 : -1;
-
- let offset = this.depth * -100;
- if (Language.get("wcf.global.pageDirection") === "rtl") {
- // reverse logic for RTL
- offset *= -1;
- }
-
- const child = this.menu.children[0] as HTMLElement;
- child.style.setProperty("transform", `translateX(${offset}%)`, "");
- }
-
- protected updateButtonState(): void {
- let hasNewContent = false;
- const itemList = this.menu.querySelector(".menuOverlayItemList");
- this.menu.querySelectorAll(".badgeUpdate").forEach((badge) => {
- const value = badge.textContent!;
- if (~~value > 0 && badge.closest(".menuOverlayItemList") === itemList) {
- hasNewContent = true;
- }
- });
-
- this.button.classList[hasNewContent ? "add" : "remove"]("pageMenuMobileButtonHasContent");
- }
-}
-
-Core.enableLegacyInheritance(UiPageMenuAbstract);
-
-export = UiPageMenuAbstract;
{include file='pageFooter'}
</div>
-{if $__isRescueMode|empty}{include file='pageMenuMobile'}{/if}
-
{event name='footer'}
<!-- JAVASCRIPT_RELOCATE_POSITION -->
+++ /dev/null
-{if PACKAGE_ID && $__wcf->user->userID}
- {* main menu *}
- <div id="pageMainMenuMobile" class="pageMainMenuMobile menuOverlayMobile" data-page-logo="{$__wcf->getPath()}acp/images/woltlabSuite.png">
- <ol class="menuOverlayItemList" data-title="{lang}wcf.menu.page{/lang}">
- <li class="menuOverlayTitle">{lang}wcf.menu.page{/lang}</li>
- {foreach from=$__wcf->getACPMenu()->getMenuItems('') item=_sectionMenuItem}
- <li class="menuOverlayItem">
- <a href="#" class="menuOverlayItemLink box24{if $_sectionMenuItem->menuItem|in_array:$_activeMenuItems} active{/if}">
- <span class="icon icon24 {$_sectionMenuItem->icon}"></span>
- <span class="menuOverlayItemTitle">{@$_sectionMenuItem}</span>
- </a>
- <ol class="menuOverlayItemList">
- {foreach from=$__wcf->getACPMenu()->getMenuItems($_sectionMenuItem->menuItem) item=_menuItemCategory}
- <li class="menuOverlayTitle">{@$_menuItemCategory}</li>
- {foreach from=$__wcf->getACPMenu()->getMenuItems($_menuItemCategory->menuItem) item=_menuItem}
- {assign var=_subMenuItems value=$__wcf->getACPMenu()->getMenuItems($_menuItem->menuItem)}
-
- {if $_subMenuItems|empty}
- <li class="menuOverlayItem{if $_menuItem->menuItem|in_array:$_activeMenuItems} active{/if}"><a href="{$_menuItem->getLink()}" class="menuOverlayItemLink">{@$_menuItem}</a></li>
- {else}
- {if $_menuItem->menuItem === 'wcf.acp.menu.link.option.category'}
- {* handle special option categories *}
- {foreach from=$_subMenuItems item=_subMenuItem}
- <li class="menuOverlayItem{if $_subMenuItem->menuItem|in_array:$_activeMenuItems} active{/if}"><a href="{$_subMenuItem->getLink()}" class="menuOverlayItemLink">{@$_subMenuItem}</a></li>
- {/foreach}
- {else}
- <li class="menuOverlayItem">
- <a href="{$_menuItem->getLink()}" class="menuOverlayItemLink{if $_menuItem->menuItem|in_array:$_activeMenuItems && $_activeMenuItems[0] === $_menuItem->menuItem} active{/if}">{@$_menuItem}</a>
-
- {foreach from=$_subMenuItems item=_subMenuItem}
- <a href="{$_subMenuItem->getLink()}" class="menuOverlayItemLinkIcon{if $_subMenuItem->menuItem|in_array:$_activeMenuItems} active{/if}"><span class="icon icon24 {$_subMenuItem->icon}"></span></a>
- {/foreach}
- </li>
- {/if}
- {/if}
- {/foreach}
- {/foreach}
- </ol>
- </li>
- {/foreach}
- </ol>
- </div>
-
- {* user menu *}
- <div id="pageUserMenuMobile" class="pageUserMenuMobile menuOverlayMobile" data-page-logo="{$__wcf->getPath()}acp/images/woltlabSuite.png">
- <ol class="menuOverlayItemList" data-title="{lang}wcf.menu.user{/lang}">
- {event name='userMenuBefore'}
-
- <li class="menuOverlayTitle">{lang}wcf.menu.user{/lang}</li>
- <li class="menuOverlayItem">
- <a href="#" class="menuOverlayItemLink box24">
- <span class="icon icon24 fa-home"></span>
- <span class="menuOverlayItemTitle">{lang}wcf.global.jumpToPage{/lang}</span>
- </a>
- <ol class="menuOverlayItemList">
- {foreach from=$__wcf->getFrontendMenu()->getMenuItemNodeList() item=_menuItem}
- {if !$_menuItem->parentItemID && $_menuItem->getPage()}
- <li class="menuOverlayItem"><a href="{$_menuItem->getURL()}" class="menuOverlayItemLink">{$_menuItem->getTitle()}</a></li>
- {/if}
- {/foreach}
- </ol>
- </li>
- <li class="menuOverlayItem">
- <a href="#" class="menuOverlayItemLink box24">
- <span class="icon icon24 fa-info"></span>
- <span class="menuOverlayItemTitle">WoltLab®</span>
- </a>
- <ol class="menuOverlayItemList">
- <li class="menuOverlayItem"><a href="https://www.woltlab.com/{if $__wcf->getLanguage()->getFixedLanguageCode() === 'de'}de/{/if}" class="menuOverlayItemLink"{if EXTERNAL_LINK_TARGET_BLANK} target="_blank" rel="noopener"{/if}>{lang}wcf.acp.index.woltlab.website{/lang}</a></li>
- <li class="menuOverlayItem"><a href="https://manual.woltlab.com/{if $__wcf->getLanguage()->getFixedLanguageCode() === 'de'}de{else}en{/if}/" class="menuOverlayItemLink"{if EXTERNAL_LINK_TARGET_BLANK} target="_blank" rel="noopener"{/if}>{lang}wcf.acp.index.woltlab.manual{/lang}</a></li>
- <li class="menuOverlayItem"><a href="https://community.woltlab.com" class="menuOverlayItemLink"{if EXTERNAL_LINK_TARGET_BLANK} target="_blank" rel="noopener"{/if}>{lang}wcf.acp.index.woltlab.forums{/lang}</a></li>
- <li class="menuOverlayItem"><a href="https://www.woltlab.com/ticket-add/" class="menuOverlayItemLink"{if EXTERNAL_LINK_TARGET_BLANK} target="_blank" rel="noopener"{/if}>{lang}wcf.acp.index.woltlab.tickets{/lang}</a></li>
- <li class="menuOverlayItem"><a href="https://pluginstore.woltlab.com" class="menuOverlayItemLink"{if EXTERNAL_LINK_TARGET_BLANK} target="_blank" rel="noopener"{/if}>{lang}wcf.acp.index.woltlab.pluginStore{/lang}</a></li>
- </ol>
- </li>
- <li class="menuOverlayTitle">{$__wcf->user->username}</li>
- <li class="menuOverlayItem">
- <a href="{link controller='Logout'}t={csrfToken type=url}{/link}" class="menuOverlayItemLink box24">
- <span class="icon icon24 fa-sign-out"></span>
- <span class="menuOverlayItemTitle">{lang}wcf.user.logout{/lang}</span>
- </a>
- </li>
-
- {event name='userMenuAfter'}
- </ol>
- </div>
-{/if}
+++ /dev/null
-/**
- * Provides a touch-friendly fullscreen menu.
- *
- * @author Alexander Ebert
- * @copyright 2001-2019 WoltLab GmbH
- * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @module WoltLabSuite/Core/Ui/Page/Menu/Abstract
- */
-define(["require", "exports", "tslib", "../../../Core", "../../../Environment", "../../../Event/Handler", "../../../Language", "../../../Dom/Traverse", "../../Screen"], function (require, exports, tslib_1, Core, Environment, EventHandler, Language, DomTraverse, UiScreen) {
- "use strict";
- Core = (0, tslib_1.__importStar)(Core);
- Environment = (0, tslib_1.__importStar)(Environment);
- EventHandler = (0, tslib_1.__importStar)(EventHandler);
- Language = (0, tslib_1.__importStar)(Language);
- DomTraverse = (0, tslib_1.__importStar)(DomTraverse);
- UiScreen = (0, tslib_1.__importStar)(UiScreen);
- const _pageContainer = document.getElementById("pageContainer");
- /**
- * Which edge of the menu is touched? Empty string
- * if no menu is currently touched.
- *
- * One 'left', 'right' or ''.
- */
- let _androidTouching = "";
- class UiPageMenuAbstract {
- constructor(eventIdentifier, elementId, buttonSelector) {
- this.activeList = [];
- this.depth = 0;
- this.enabled = true;
- this.items = new Map();
- this.removeActiveList = false;
- if (document.body.dataset.template === "packageInstallationSetup") {
- // work-around for WCFSetup on mobile
- return;
- }
- this.eventIdentifier = eventIdentifier;
- this.menu = document.getElementById(elementId);
- const callbackOpen = this.open.bind(this);
- this.button = document.querySelector(buttonSelector);
- this.button.addEventListener("click", callbackOpen);
- this.initItems();
- this.initHeader();
- EventHandler.add(this.eventIdentifier, "open", callbackOpen);
- EventHandler.add(this.eventIdentifier, "close", this.close.bind(this));
- EventHandler.add(this.eventIdentifier, "updateButtonState", this.updateButtonState.bind(this));
- this.menu.addEventListener("animationend", () => {
- if (!this.menu.classList.contains("open")) {
- this.menu.querySelectorAll(".menuOverlayItemList").forEach((itemList) => {
- // force the main list to be displayed
- itemList.classList.remove("active", "hidden");
- });
- }
- });
- this.menu.children[0].addEventListener("transitionend", () => {
- this.menu.classList.add("allowScroll");
- if (this.removeActiveList) {
- this.removeActiveList = false;
- const list = this.activeList.pop();
- if (list) {
- list.classList.remove("activeList");
- }
- }
- });
- const backdrop = document.createElement("div");
- backdrop.className = "menuOverlayMobileBackdrop";
- backdrop.addEventListener("click", this.close.bind(this));
- this.menu.insertAdjacentElement("afterend", backdrop);
- this.menu.parentElement.insertBefore(backdrop, this.menu.nextSibling);
- this.updateButtonState();
- if (Environment.platform() === "android") {
- this.initializeAndroid();
- }
- }
- /**
- * Opens the menu.
- */
- open(event) {
- if (!this.enabled) {
- return false;
- }
- if (event instanceof Event) {
- event.preventDefault();
- }
- this.menu.classList.add("open");
- this.menu.classList.add("allowScroll");
- this.menu.children[0].classList.add("activeList");
- UiScreen.scrollDisable();
- _pageContainer.classList.add("menuOverlay-" + this.menu.id);
- UiScreen.pageOverlayOpen();
- return true;
- }
- /**
- * Closes the menu.
- */
- close(event) {
- if (event instanceof Event) {
- event.preventDefault();
- }
- if (this.menu.classList.contains("open")) {
- this.menu.classList.remove("open");
- UiScreen.scrollEnable();
- UiScreen.pageOverlayClose();
- _pageContainer.classList.remove("menuOverlay-" + this.menu.id);
- return true;
- }
- return false;
- }
- /**
- * Enables the touch menu.
- */
- enable() {
- this.enabled = true;
- }
- /**
- * Disables the touch menu.
- */
- disable() {
- this.enabled = false;
- this.close();
- }
- /**
- * Initializes the Android Touch Menu.
- */
- initializeAndroid() {
- // specify on which side of the page the menu appears
- let appearsAt;
- switch (this.menu.id) {
- case "pageUserMenuMobile":
- appearsAt = "right";
- break;
- case "pageMainMenuMobile":
- appearsAt = "left";
- break;
- default:
- return;
- }
- const backdrop = this.menu.nextElementSibling;
- // horizontal position of the touch start
- let touchStart = undefined;
- document.addEventListener("touchstart", (event) => {
- const touches = event.touches;
- let isLeftEdge;
- let isRightEdge;
- const isOpen = this.menu.classList.contains("open");
- // check whether we touch the edges of the menu
- if (appearsAt === "left") {
- isLeftEdge = !isOpen && touches[0].clientX < 20 /* AtEdge */;
- isRightEdge = isOpen && Math.abs(this.menu.offsetWidth - touches[0].clientX) < 20 /* AtEdge */;
- }
- else {
- isLeftEdge =
- isOpen &&
- Math.abs(document.body.clientWidth - this.menu.offsetWidth - touches[0].clientX) < 20 /* AtEdge */;
- isRightEdge = !isOpen && document.body.clientWidth - touches[0].clientX < 20 /* AtEdge */;
- }
- // abort if more than one touch
- if (touches.length > 1) {
- if (_androidTouching) {
- Core.triggerEvent(document, "touchend");
- }
- return;
- }
- // break if a touch is in progress
- if (_androidTouching) {
- return;
- }
- // break if no edge has been touched
- if (!isLeftEdge && !isRightEdge) {
- return;
- }
- // break if a different menu is open
- if (UiScreen.pageOverlayIsActive()) {
- const found = _pageContainer.classList.contains(`menuOverlay-${this.menu.id}`);
- if (!found) {
- return;
- }
- }
- // break if redactor is in use
- if (document.documentElement.classList.contains("redactorActive")) {
- return;
- }
- touchStart = {
- x: touches[0].clientX,
- y: touches[0].clientY,
- };
- if (isLeftEdge) {
- _androidTouching = "left";
- }
- if (isRightEdge) {
- _androidTouching = "right";
- }
- });
- document.addEventListener("touchend", (event) => {
- // break if we did not start a touch
- if (!_androidTouching || !touchStart) {
- return;
- }
- // break if the menu did not even start opening
- if (!this.menu.classList.contains("open")) {
- // reset
- touchStart = undefined;
- _androidTouching = "";
- return;
- }
- // last known position of the finger
- let position;
- if (event) {
- position = event.changedTouches[0].clientX;
- }
- else {
- position = touchStart.x;
- }
- // clean up touch styles
- this.menu.classList.add("androidMenuTouchEnd");
- this.menu.style.removeProperty("transform");
- backdrop.style.removeProperty(appearsAt);
- this.menu.addEventListener("transitionend", () => {
- this.menu.classList.remove("androidMenuTouchEnd");
- }, { once: true });
- // check whether the user moved the finger far enough
- if (appearsAt === "left") {
- if (_androidTouching === "left" && position < touchStart.x + 100) {
- this.close();
- }
- if (_androidTouching === "right" && position < touchStart.x - 100) {
- this.close();
- }
- }
- else {
- if (_androidTouching === "left" && position > touchStart.x + 100) {
- this.close();
- }
- if (_androidTouching === "right" && position > touchStart.x - 100) {
- this.close();
- }
- }
- // reset
- touchStart = undefined;
- _androidTouching = "";
- });
- document.addEventListener("touchmove", (event) => {
- // break if we did not start a touch
- if (!_androidTouching || !touchStart) {
- return;
- }
- const touches = event.touches;
- // check whether the user started moving in the correct direction
- // this avoids false positives, in case the user just wanted to tap
- let movedFromEdge = false;
- if (_androidTouching === "left") {
- movedFromEdge = touches[0].clientX > touchStart.x + 5 /* MovedHorizontally */;
- }
- if (_androidTouching === "right") {
- movedFromEdge = touches[0].clientX < touchStart.x - 5 /* MovedHorizontally */;
- }
- const movedVertically = Math.abs(touches[0].clientY - touchStart.y) > 20 /* MovedVertically */;
- let isOpen = this.menu.classList.contains("open");
- if (!isOpen && movedFromEdge && !movedVertically) {
- // the menu is not yet open, but the user moved into the right direction
- this.open();
- isOpen = true;
- }
- if (isOpen) {
- // update CSS to the new finger position
- let position = touches[0].clientX;
- if (appearsAt === "right") {
- position = document.body.clientWidth - position;
- }
- if (position > this.menu.offsetWidth) {
- position = this.menu.offsetWidth;
- }
- if (position < 0) {
- position = 0;
- }
- const offset = (appearsAt === "left" ? 1 : -1) * (position - this.menu.offsetWidth);
- this.menu.style.setProperty("transform", `translateX(${offset}px)`);
- backdrop.style.setProperty(appearsAt, Math.min(this.menu.offsetWidth, position).toString() + "px");
- }
- });
- }
- /**
- * Initializes all menu items.
- */
- initItems() {
- this.menu.querySelectorAll(".menuOverlayItemLink").forEach((element) => {
- this.initItem(element);
- });
- }
- /**
- * Initializes a single menu item.
- */
- initItem(item) {
- // check if it should contain a 'more' link w/ an external callback
- const parent = item.parentElement;
- const more = parent.dataset.more;
- if (more) {
- item.addEventListener("click", (event) => {
- event.preventDefault();
- event.stopPropagation();
- EventHandler.fire(this.eventIdentifier, "more", {
- handler: this,
- identifier: more,
- item: item,
- parent: parent,
- });
- });
- return;
- }
- const itemList = item.nextElementSibling;
- if (itemList === null) {
- return;
- }
- // handle static items with an icon-type button next to it (acp menu)
- if (itemList.nodeName !== "OL" && itemList.classList.contains("menuOverlayItemLinkIcon")) {
- // add wrapper
- const wrapper = document.createElement("span");
- wrapper.className = "menuOverlayItemWrapper";
- parent.insertBefore(wrapper, item);
- wrapper.appendChild(item);
- while (wrapper.nextElementSibling) {
- wrapper.appendChild(wrapper.nextElementSibling);
- }
- return;
- }
- const isLink = item.getAttribute("href") !== "#";
- const parentItemList = parent.parentElement;
- let itemTitle = itemList.dataset.title;
- this.items.set(item, {
- itemList: itemList,
- parentItemList: parentItemList,
- });
- if (!itemTitle) {
- itemTitle = DomTraverse.childByClass(item, "menuOverlayItemTitle").textContent;
- itemList.dataset.title = itemTitle;
- }
- const callbackLink = this.showItemList.bind(this, item);
- if (isLink) {
- const wrapper = document.createElement("span");
- wrapper.className = "menuOverlayItemWrapper";
- parent.insertBefore(wrapper, item);
- wrapper.appendChild(item);
- const moreLink = document.createElement("a");
- moreLink.href = "#";
- moreLink.className = "menuOverlayItemLinkIcon" + (item.classList.contains("active") ? " active" : "");
- moreLink.innerHTML = '<span class="icon icon24 fa-angle-right"></span>';
- moreLink.addEventListener("click", callbackLink);
- wrapper.appendChild(moreLink);
- }
- else {
- item.classList.add("menuOverlayItemLinkMore");
- item.addEventListener("click", callbackLink);
- }
- const backLinkItem = document.createElement("li");
- backLinkItem.className = "menuOverlayHeader";
- const wrapper = document.createElement("span");
- wrapper.className = "menuOverlayItemWrapper";
- const backLink = document.createElement("a");
- backLink.href = "#";
- backLink.className = "menuOverlayItemLink menuOverlayBackLink";
- backLink.textContent = parentItemList.dataset.title || "";
- backLink.addEventListener("click", this.hideItemList.bind(this, item));
- const closeLink = document.createElement("a");
- closeLink.href = "#";
- closeLink.className = "menuOverlayItemLinkIcon";
- closeLink.innerHTML = '<span class="icon icon24 fa-times"></span>';
- closeLink.addEventListener("click", this.close.bind(this));
- wrapper.appendChild(backLink);
- wrapper.appendChild(closeLink);
- backLinkItem.appendChild(wrapper);
- itemList.insertBefore(backLinkItem, itemList.firstElementChild);
- if (!backLinkItem.nextElementSibling.classList.contains("menuOverlayTitle")) {
- const titleItem = document.createElement("li");
- titleItem.className = "menuOverlayTitle";
- const title = document.createElement("span");
- title.textContent = itemTitle;
- titleItem.appendChild(title);
- itemList.insertBefore(titleItem, backLinkItem.nextElementSibling);
- }
- }
- /**
- * Renders the menu item list header.
- */
- initHeader() {
- const listItem = document.createElement("li");
- listItem.className = "menuOverlayHeader";
- const wrapper = document.createElement("span");
- wrapper.className = "menuOverlayItemWrapper";
- listItem.appendChild(wrapper);
- const logoWrapper = document.createElement("span");
- logoWrapper.className = "menuOverlayLogoWrapper";
- wrapper.appendChild(logoWrapper);
- const logo = document.createElement("span");
- logo.className = "menuOverlayLogo";
- const pageLogo = this.menu.dataset.pageLogo;
- logo.style.setProperty("background-image", `url("${pageLogo}")`, "");
- logoWrapper.appendChild(logo);
- const closeLink = document.createElement("a");
- closeLink.href = "#";
- closeLink.className = "menuOverlayItemLinkIcon";
- closeLink.innerHTML = '<span class="icon icon24 fa-times"></span>';
- closeLink.addEventListener("click", this.close.bind(this));
- wrapper.appendChild(closeLink);
- const list = DomTraverse.childByClass(this.menu, "menuOverlayItemList");
- list.insertBefore(listItem, list.firstElementChild);
- }
- /**
- * Hides an item list, return to the parent item list.
- */
- hideItemList(item, event) {
- if (event instanceof Event) {
- event.preventDefault();
- }
- this.menu.classList.remove("allowScroll");
- this.removeActiveList = true;
- const data = this.items.get(item);
- data.parentItemList.classList.remove("hidden");
- this.updateDepth(false);
- }
- /**
- * Shows the child item list.
- */
- showItemList(item, event) {
- event.preventDefault();
- const data = this.items.get(item);
- const load = data.itemList.dataset.load;
- if (load) {
- if (!Core.stringToBool(item.dataset.loaded || "")) {
- const target = event.currentTarget;
- const icon = target.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;
- }
- }
- this.menu.classList.remove("allowScroll");
- data.itemList.classList.add("activeList");
- data.parentItemList.classList.add("hidden");
- this.activeList.push(data.itemList);
- this.updateDepth(true);
- }
- updateDepth(increase) {
- this.depth += increase ? 1 : -1;
- let offset = this.depth * -100;
- if (Language.get("wcf.global.pageDirection") === "rtl") {
- // reverse logic for RTL
- offset *= -1;
- }
- const child = this.menu.children[0];
- child.style.setProperty("transform", `translateX(${offset}%)`, "");
- }
- updateButtonState() {
- let hasNewContent = false;
- const itemList = this.menu.querySelector(".menuOverlayItemList");
- this.menu.querySelectorAll(".badgeUpdate").forEach((badge) => {
- const value = badge.textContent;
- if (~~value > 0 && badge.closest(".menuOverlayItemList") === itemList) {
- hasNewContent = true;
- }
- });
- this.button.classList[hasNewContent ? "add" : "remove"]("pageMenuMobileButtonHasContent");
- }
- }
- Core.enableLegacyInheritance(UiPageMenuAbstract);
- return UiPageMenuAbstract;
-});
+++ /dev/null
-/* menu container */
-.menuOverlayMobile {
- background-color: $wcfHeaderMenuBackground;
- bottom: 0;
- display: none;
- overflow: hidden;
- position: absolute;
- top: 0;
- z-index: 320;
-
- &.open {
- display: block;
-
- @include screen-sm-up {
- + .menuOverlayMobileBackdrop {
- display: block;
- }
- }
- }
-
- &.androidMenuTouchEnd {
- display: block;
- position: fixed;
- transition: transform 0.24s cubic-bezier(0.25, 0.46, 0.45, 0.94);
-
- &.pageMainMenuMobile:not(.open) {
- transform: translateX(-100vw);
- }
- &.pageUserMenuMobile:not(.open) {
- transform: translateX(100vw);
- }
-
- @include screen-sm-up {
- &.pageMainMenuMobile:not(.open) {
- transform: translateX(-350px);
- }
- &.pageUserMenuMobile:not(.open) {
- transform: translateX(350px);
- }
-
- + .menuOverlayMobileBackdrop {
- transition: left 0.24s cubic-bezier(0.25, 0.46, 0.45, 0.94),
- right 0.24s cubic-bezier(0.25, 0.46, 0.45, 0.94);
- }
- }
- }
-
- > .menuOverlayItemList {
- // we use `transform: translateX()` for performance reasons
- transition: transform 0.24s cubic-bezier(0.25, 0.46, 0.45, 0.94);
-
- /* work-around to avoid setting explicit visibility */
- visibility: visible;
- }
-
- &.allowScroll {
- .menuOverlayItemList:not(.hidden) {
- overflow: auto;
- -webkit-overflow-scrolling: touch;
- }
- }
-
- &:not(.allowScroll) {
- // block UI while switching between menus
- &::before {
- bottom: 0;
- content: "";
- left: 0;
- position: absolute;
- right: 0;
- top: 0;
- z-index: 500;
- }
- }
-
- @include screen-xs {
- left: 0;
- max-width: 100vw;
- right: 0;
-
- .menuOverlayItemList {
- right: 0;
- }
- }
-
- @include screen-sm-up {
- width: 350px;
-
- &.pageMainMenuMobile {
- left: 0;
-
- & + .menuOverlayMobileBackdrop {
- box-shadow: inset 5px 0 10px -5px rgba(0, 0, 0, 0.6);
- left: 350px;
- }
-
- .menuOverlayItemList {
- left: 0;
- }
- }
-
- &.pageUserMenuMobile {
- right: 0;
-
- & + .menuOverlayMobileBackdrop {
- box-shadow: inset -5px 0 10px -5px rgba(0, 0, 0, 0.6);
- right: 350px;
- }
-
- .menuOverlayItemList {
- right: 0;
- }
- }
- }
-}
-
-.menuOverlayMobileBackdrop {
- background-color: rgba(0, 0, 0, 0.4);
- bottom: 0;
- display: none;
- left: 0;
- position: fixed;
- right: 0;
- top: 0;
- z-index: 395;
-}
-
-.menuOverlayItemWrapper {
- display: flex;
- justify-content: flex-end;
-
- > .menuOverlayItemLink {
- flex: 1 1 auto;
- }
-}
-
-.menuOverlayItemList {
- background-color: $wcfHeaderMenuBackground;
- height: 100%;
- list-style-type: none;
- margin: 0;
- padding: 5px 0;
- position: absolute;
- top: 0;
- left: -1px;
- bottom: 0;
- width: calc(100vw + 1px);
- z-index: 450;
-
- @include screen-sm-up {
- width: 350px;
- }
-
- &:not(.activeList) {
- display: none;
- }
-}
-
-.menuOverlayItemSpacer {
- margin-top: 25px;
-
- /* avoid successive spacers piling up */
- & + .menuOverlayItemSpacer {
- display: none;
- }
-}
-
-.menuOverlayItem {
- &:not(:last-child) {
- margin-bottom: 1px;
- }
-
- /* nested item list */
- > .menuOverlayItemList {
- margin-left: 100%;
- z-index: 500;
- }
-}
-
-.menuOverlayItemLink,
-.menuOverlayTitle,
-.menuOverlayBackLink {
- color: $wcfHeaderMenuLink;
- display: block;
- font-size: 14px;
- padding: 10px 30px;
- position: relative;
-}
-
-.menuOverlayItemLink {
- align-items: center;
- background-color: $wcfHeaderMenuLinkBackground;
- display: flex;
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
-
- .icon::before {
- color: $wcfHeaderMenuLink;
- }
-
- &:hover {
- color: $wcfHeaderMenuLinkActive;
- }
-
- /* 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;
- }
-
- > .badge {
- flex: 0 0 auto;
- }
- }
-
- &.menuOverlayItemLinkMore::after {
- color: $wcfHeaderMenuLink;
- content: $fa-var-angle-right;
- display: block;
- font-family: FontAwesome;
- font-size: 18px;
- position: absolute;
- right: 18px;
- top: 50%;
- transform: translateY(-50%);
- }
-
- .menuOverlayItemTitle {
- overflow: hidden;
- text-overflow: ellipsis;
- }
-}
-
-/* fix icons in rtl design */
-html[dir="rtl"] .menuOverlayItemLink.menuOverlayItemLinkMore::after {
- content: $fa-var-angle-left;
-}
-
-.pageUserMenuMobile .menuOverlayItemBadge:last-child {
- padding-right: 10px !important;
-}
-
-.menuOverlayItemLink.active,
-.menuOverlayItemLinkIcon.active {
- background-color: $wcfHeaderMenuLinkBackgroundActive;
- color: $wcfHeaderMenuLinkActive;
-}
-
-.menuOverlayItemLinkIcon.active > .icon::before {
- color: $wcfHeaderMenuLinkActive;
-}
-
-.menuOverlayTitle {
- color: rgba($wcfHeaderMenuLink, 0.7);
- 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: $wcfHeaderMenuLinkBackground;
- 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: $wcfHeaderMenuLink;
- }
-}
-
-.menuOverlayBackLink::before {
- color: rgba($wcfHeaderMenuLink, 0.7);
- content: $fa-var-angle-left;
- display: block;
- font-family: FontAwesome;
- font-size: 18px;
- position: absolute;
- left: 12px;
- 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;
- }
-}