From: Alexander Ebert Date: Sun, 12 Dec 2021 13:49:03 +0000 (+0100) Subject: Prototype for a new mobile menu X-Git-Tag: 5.5.0_Alpha_1~242^2~46 X-Git-Url: https://git.stricted.de/?a=commitdiff_plain;h=37f9d16b9f0fc3a13861c42767901d40486da55b;p=GitHub%2FWoltLab%2FWCF.git Prototype for a new mobile menu --- diff --git a/com.woltlab.wcf/templates/headIncludeJavaScript.tpl b/com.woltlab.wcf/templates/headIncludeJavaScript.tpl index 947a8e640b..c4d8f38d48 100644 --- a/com.woltlab.wcf/templates/headIncludeJavaScript.tpl +++ b/com.woltlab.wcf/templates/headIncludeJavaScript.tpl @@ -16,6 +16,7 @@ var ENABLE_PRODUCTION_DEBUG_MODE = {if ENABLE_PRODUCTION_DEBUG_MODE}true{else}false{/if}; var ENABLE_DEVELOPER_TOOLS = {if ENABLE_DEVELOPER_TOOLS}true{else}false{/if}; var WSC_API_VERSION = {@WSC_API_VERSION}; + var PAGE_TITLE = '{PAGE_TITLE|language|encodeJS}'; var REACTION_TYPES = {@$__wcf->getReactionHandler()->getReactionsJSVariable()}; diff --git a/com.woltlab.wcf/templates/header.tpl b/com.woltlab.wcf/templates/header.tpl index d9f66db755..5de4b28037 100644 --- a/com.woltlab.wcf/templates/header.tpl +++ b/com.woltlab.wcf/templates/header.tpl @@ -12,6 +12,10 @@ {include file='headInclude'} + + {if !$canonicalURL|empty} {/if} diff --git a/com.woltlab.wcf/templates/pageHeader.tpl b/com.woltlab.wcf/templates/pageHeader.tpl index 8512fbbf1a..4887cec363 100644 --- a/com.woltlab.wcf/templates/pageHeader.tpl +++ b/com.woltlab.wcf/templates/pageHeader.tpl @@ -20,6 +20,9 @@ require(['WoltLabSuite/Core/Ui/Page/Header/Fixed'], function(UiPageHeaderFixed) { UiPageHeaderFixed.init(); }); + require(["WoltLabSuite/Core/Ui/Page/Menu/Main"], ({ PageMenuMain }) => { + new PageMenuMain(); + }) diff --git a/global.d.ts b/global.d.ts index 0f1e135d76..fec4b85e6f 100644 --- a/global.d.ts +++ b/global.d.ts @@ -13,6 +13,7 @@ declare global { ENABLE_DEBUG_MODE: boolean; ENABLE_DEVELOPER_TOOLS: boolean; LANGUAGE_ID: number; + PAGE_TITLE: string; REACTION_TYPES: { [key: string]: Reaction; }; diff --git a/ts/WoltLabSuite/Core/Ui/Mobile.ts b/ts/WoltLabSuite/Core/Ui/Mobile.ts index bda5ab1f35..3f15dc50e1 100644 --- a/ts/WoltLabSuite/Core/Ui/Mobile.ts +++ b/ts/WoltLabSuite/Core/Ui/Mobile.ts @@ -14,13 +14,13 @@ import * as EventHandler from "../Event/Handler"; import * as UiAlignment from "./Alignment"; import UiCloseOverlay from "./CloseOverlay"; import * as UiDropdownReusable from "./Dropdown/Reusable"; -import UiPageMenuMain from "./Page/Menu/Main"; +//import UiPageMenuMain from "./Page/Menu/Main"; import UiPageMenuUser from "./Page/Menu/User"; import * as UiScreen from "./Screen"; interface MainMenuMorePayload { identifier: string; - handler: UiPageMenuMain; + handler: any;//UiPageMenuMain; } let _dropdownMenu: HTMLUListElement | null = null; @@ -30,7 +30,7 @@ let _enabledLGTouchNavigation = false; let _enableMobileMenu = false; const _knownMessages = new WeakSet(); let _mobileSidebarEnabled = false; -let _pageMenuMain: UiPageMenuMain; +//let _pageMenuMain: UiPageMenuMain; let _pageMenuUser: UiPageMenuUser | undefined = undefined; let _messageGroups: HTMLCollection | null = null; const _sidebars: HTMLElement[] = []; @@ -163,7 +163,7 @@ function initMessages(): void { function initMobileMenu(): void { if (_enableMobileMenu) { - _pageMenuMain = new UiPageMenuMain(); + //_pageMenuMain = new UiPageMenuMain(); if (UiPageMenuUser.hasValidMenu()) { _pageMenuUser = new UiPageMenuUser(); @@ -378,7 +378,7 @@ export function setup(enableMobileMenu: boolean): void { export function enable(): void { _enabled = true; if (_enableMobileMenu) { - _pageMenuMain.enable(); + //_pageMenuMain.enable(); _pageMenuUser?.enable(); } } @@ -398,7 +398,7 @@ export function enableShadow(): void { export function disable(): void { _enabled = false; if (_enableMobileMenu) { - _pageMenuMain.disable(); + //_pageMenuMain.disable(); _pageMenuUser?.disable(); } } diff --git a/ts/WoltLabSuite/Core/Ui/Page/Menu/Container.ts b/ts/WoltLabSuite/Core/Ui/Page/Menu/Container.ts new file mode 100644 index 0000000000..7aa8eca9e7 --- /dev/null +++ b/ts/WoltLabSuite/Core/Ui/Page/Menu/Container.ts @@ -0,0 +1,76 @@ +import { PageMenuProvider } from "./Provider"; +import { createFocusTrap, FocusTrap } from "focus-trap"; +import { scrollDisable, scrollEnable } from "../../Screen"; + +export class PageMenuContainer { + private readonly container = document.createElement("div"); + private readonly content = document.createElement("div"); + private focusTrap?: FocusTrap = undefined; + private readonly provider: PageMenuProvider; + + constructor(provider: PageMenuProvider) { + this.provider = provider; + } + + open(): void { + this.buildElements(); + + this.content.innerHTML = ""; + this.content.append(this.provider.getContent()); + this.provider.getMenuButton().setAttribute("aria-expanded", "true"); + + scrollDisable(); + + this.container.hidden = false; + this.getFocusTrap().activate(); + } + + close(): void { + this.provider.getMenuButton().setAttribute("aria-expanded", "false"); + + scrollEnable(); + + this.container.hidden = true; + this.getFocusTrap().deactivate(); + } + + toggle(): void { + if (this.container.hidden) { + this.open(); + } else { + this.close(); + } + } + + private buildElements(): void { + if (this.container.classList.contains("pageMenuContainer")) { + return; + } + + this.container.classList.add("pageMenuContainer"); + this.container.hidden = true; + this.container.addEventListener("click", (event) => { + if (event.target === this.container) { + this.close(); + } + }); + + this.content.classList.add("pageMenuContent"); + + this.container.append(this.content); + + document.body.append(this.container); + } + + private getFocusTrap(): FocusTrap { + if (this.focusTrap === undefined) { + this.focusTrap = createFocusTrap(this.content, { + allowOutsideClick: true, + }); + } + + return this.focusTrap; + } +} + +export default PageMenuContainer; diff --git a/ts/WoltLabSuite/Core/Ui/Page/Menu/Main.ts b/ts/WoltLabSuite/Core/Ui/Page/Menu/Main.ts index 68194d366c..e1ca3e1d5b 100644 --- a/ts/WoltLabSuite/Core/Ui/Page/Menu/Main.ts +++ b/ts/WoltLabSuite/Core/Ui/Page/Menu/Main.ts @@ -1,111 +1,204 @@ /** * Provides the touch-friendly fullscreen main menu. * - * @author Alexander Ebert - * @copyright 2001-2019 WoltLab GmbH - * @license GNU Lesser General Public License - * @module WoltLabSuite/Core/Ui/Page/Menu/Main + * @author Alexander Ebert + * @copyright 2001-2021 WoltLab GmbH + * @license GNU Lesser General Public License + * @module WoltLabSuite/Core/Ui/Page/Menu/Main */ -import * as Core from "../../../Core"; -import DomUtil from "../../../Dom/Util"; +import PageMenuContainer from "./Container"; +import { PageMenuProvider } from "./Provider"; import * as Language from "../../../Language"; -import UiPageMenuAbstract from "./Abstract"; +import DomUtil from "../../../Dom/Util"; -class UiPageMenuMain extends UiPageMenuAbstract { - private hasItems = false; - private readonly navigationList: HTMLOListElement; - private readonly title: HTMLElement; +type MenuItem = { + active: boolean; + children: MenuItem[]; + counter: number; + link?: string; + title: string; +}; + +function normalizeMenuItem(menuItem: HTMLElement): MenuItem { + const anchor = menuItem.querySelector(".boxMenuLink") as HTMLAnchorElement; + const title = anchor.querySelector(".boxMenuLinkTitle")!.textContent as string; + + let counter = 0; + const outstandingItems = anchor.querySelector(".boxMenuLinkOutstandingItems"); + if (outstandingItems) { + counter = +outstandingItems.textContent!.replace(/[^0-9]/, ""); + } + + const subMenu = menuItem.querySelector("ol"); + let children: MenuItem[] = []; + if (subMenu instanceof HTMLOListElement) { + children = Array.from(subMenu.children).map((subMenuItem: HTMLElement) => { + return normalizeMenuItem(subMenuItem); + }); + } + + // `link.href` represents the computed link, not the raw value. + const href = anchor.getAttribute("href"); + let link: string | undefined = undefined; + if (href && href !== "#") { + link = anchor.href; + } + + const active = menuItem.classList.contains("active"); + + return { + active, + children, + counter, + link, + title, + }; +} + +export class PageMenuMain implements PageMenuProvider { + private readonly container: PageMenuContainer; + private readonly mainMenu: HTMLElement; - /** - * Initializes the touch-friendly fullscreen main menu. - */ constructor() { - super("com.woltlab.wcf.MainMenuMobile", "pageMainMenuMobile", "#pageHeader .mainMenu"); + this.mainMenu = document.querySelector(".mainMenu")!; - this.title = document.getElementById("pageMainMenuMobilePageOptionsTitle") as HTMLElement; - if (this.title !== null) { - this.navigationList = document.querySelector(".jsPageNavigationIcons") as HTMLOListElement; - } + this.container = new PageMenuContainer(this); + + this.mainMenu.addEventListener("click", (event) => { + event.preventDefault(); - this.button.setAttribute("aria-label", Language.get("wcf.menu.page")); - this.button.setAttribute("role", "button"); + this.container.toggle(); + }); } - open(event?: MouseEvent): boolean { - if (!super.open(event)) { - return false; - } + getContent(): DocumentFragment { + const fragment = document.createDocumentFragment(); - if (this.title === null) { - return true; - } + fragment.append(...this.buildMainMenu()); + + return fragment; + } - this.hasItems = this.navigationList && this.navigationList.childElementCount > 0; + getMenuButton(): HTMLElement { + return this.mainMenu; + } + + private buildMainMenu(): HTMLElement[] { + const menu = this.mainMenu.querySelector(".boxMenu")!; + const menuItems: MenuItem[] = Array.from(menu.children).map((element: HTMLElement) => { + return normalizeMenuItem(element); + }); - if (this.hasItems) { - while (this.navigationList.childElementCount) { - const item = this.navigationList.children[0]; + const nav = document.createElement("nav"); + nav.classList.add("pageMenuMainNavigation"); + nav.setAttribute("aria-label", window.PAGE_TITLE); + nav.setAttribute("role", "navigation"); + nav.append(this.buildMenuItemList(menuItems)); - item.classList.add("menuOverlayItem", "menuOverlayItemOption"); - item.addEventListener("click", (ev) => { - ev.stopPropagation(); + return [nav]; + } - this.close(); - }); + private buildMenuItemList(menuItems: MenuItem[]): HTMLUListElement { + const list = document.createElement("ul"); + list.classList.add("pageMenuMainItemList"); - const link = item.children[0]; - link.classList.add("menuOverlayItemLink"); - link.classList.add("box24"); + menuItems + .filter((menuItem) => { + // Remove links that have no target (`#`) and do not contain any children. + if (!menuItem.link && menuItem.children.length === 0) { + return false; + } - link.children[1].classList.remove("invisible"); - link.children[1].classList.add("menuOverlayItemTitle"); + return true; + }) + .forEach((menuItem) => { + list.append(this.buildMenuItem(menuItem)); + }); + + return list; + } - this.title.insertAdjacentElement("afterend", item); + private buildMenuItem(menuItem: MenuItem): HTMLLIElement { + const listItem = document.createElement("li"); + listItem.classList.add("pageMenuMainItem"); + + if (menuItem.link) { + const link = document.createElement("a"); + link.classList.add("pageMenuMainItemLink"); + link.href = menuItem.link; + link.textContent = menuItem.title; + if (menuItem.active) { + link.setAttribute("aria-current", "page"); } - DomUtil.show(this.title); + listItem.append(link); } else { - DomUtil.hide(this.title); + const label = document.createElement("span"); + label.textContent = menuItem.title; + + listItem.append(label); } - return true; - } + if (menuItem.children.length) { + listItem.classList.add("pageMenuMainItemExpandable"); - close(event?: Event): boolean { - if (!super.close(event)) { - return false; - } + const menuId = DomUtil.getUniqueId(); - if (this.hasItems) { - DomUtil.hide(this.title); + const button = document.createElement("a"); + button.classList.add("pageMenuMainItemToggle"); + button.tabIndex = 0; + button.setAttribute("role", "button"); + button.setAttribute("aria-expanded", "false"); + button.setAttribute("aria-controls", menuId); + button.setAttribute("aria-label", Language.get("TODO")); + button.innerHTML = ''; - let item = this.title.nextElementSibling; - while (item && item.classList.contains("menuOverlayItemOption")) { - item.classList.remove("menuOverlayItem", "menuOverlayItemOption"); - item.removeEventListener("click", (ev) => { - ev.stopPropagation(); + const list = this.buildMenuItemList(menuItem.children); + list.id = menuId; + list.hidden = true; - this.close(); - }); + button.addEventListener("click", (event) => { + event.preventDefault(); - const link = item.children[0]; - link.classList.remove("menuOverlayItemLink"); - link.classList.remove("box24"); + this.toggleList(button, list); + }); + button.addEventListener("keydown", (event) => { + if (event.key === "Enter" || event.key === " ") { + event.preventDefault(); - link.children[1].classList.add("invisible"); - link.children[1].classList.remove("menuOverlayItemTitle"); + button.click(); + } + }); - this.navigationList.appendChild(item); + list.addEventListener("keydown", (event) => { + if (event.key === "Escape") { + event.preventDefault(); + event.stopPropagation(); - item = item.nextElementSibling; - } + this.toggleList(button, list); + } + }); + + listItem.append(button, list); } - return true; + return listItem; } -} -Core.enableLegacyInheritance(UiPageMenuMain); + private toggleList(button: HTMLAnchorElement, list: HTMLUListElement): void { + if (list.hidden) { + button.setAttribute("aria-expanded", "true"); + list.hidden = false; + } else { + button.setAttribute("aria-expanded", "false"); + list.hidden = true; + + if (document.activeElement !== button) { + button.focus(); + } + } + } +} -export = UiPageMenuMain; +export default PageMenuMain; diff --git a/ts/WoltLabSuite/Core/Ui/Page/Menu/Provider.ts b/ts/WoltLabSuite/Core/Ui/Page/Menu/Provider.ts new file mode 100644 index 0000000000..ac074c8ac7 --- /dev/null +++ b/ts/WoltLabSuite/Core/Ui/Page/Menu/Provider.ts @@ -0,0 +1,5 @@ +export interface PageMenuProvider { + getContent(): DocumentFragment; + + getMenuButton(): HTMLElement; +} diff --git a/wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Mobile.js b/wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Mobile.js index e89509b56d..98c916b1b6 100644 --- a/wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Mobile.js +++ b/wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Mobile.js @@ -6,7 +6,7 @@ * @license GNU Lesser General Public License * @module WoltLabSuite/Core/Ui/Mobile */ -define(["require", "exports", "tslib", "../Core", "../Dom/Change/Listener", "../Environment", "../Event/Handler", "./Alignment", "./CloseOverlay", "./Dropdown/Reusable", "./Page/Menu/Main", "./Page/Menu/User", "./Screen"], function (require, exports, tslib_1, Core, Listener_1, Environment, EventHandler, UiAlignment, CloseOverlay_1, UiDropdownReusable, Main_1, User_1, UiScreen) { +define(["require", "exports", "tslib", "../Core", "../Dom/Change/Listener", "../Environment", "../Event/Handler", "./Alignment", "./CloseOverlay", "./Dropdown/Reusable", "./Page/Menu/User", "./Screen"], function (require, exports, tslib_1, Core, Listener_1, Environment, EventHandler, UiAlignment, CloseOverlay_1, UiDropdownReusable, User_1, UiScreen) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.removeShadow = exports.rebuildShadow = exports.disableShadow = exports.disable = exports.enableShadow = exports.enable = exports.setup = void 0; @@ -17,7 +17,6 @@ define(["require", "exports", "tslib", "../Core", "../Dom/Change/Listener", "../ UiAlignment = (0, tslib_1.__importStar)(UiAlignment); CloseOverlay_1 = (0, tslib_1.__importDefault)(CloseOverlay_1); UiDropdownReusable = (0, tslib_1.__importStar)(UiDropdownReusable); - Main_1 = (0, tslib_1.__importDefault)(Main_1); User_1 = (0, tslib_1.__importDefault)(User_1); UiScreen = (0, tslib_1.__importStar)(UiScreen); let _dropdownMenu = null; @@ -27,7 +26,7 @@ define(["require", "exports", "tslib", "../Core", "../Dom/Change/Listener", "../ let _enableMobileMenu = false; const _knownMessages = new WeakSet(); let _mobileSidebarEnabled = false; - let _pageMenuMain; + //let _pageMenuMain: UiPageMenuMain; let _pageMenuUser = undefined; let _messageGroups = null; const _sidebars = []; @@ -137,7 +136,7 @@ define(["require", "exports", "tslib", "../Core", "../Dom/Change/Listener", "../ } function initMobileMenu() { if (_enableMobileMenu) { - _pageMenuMain = new Main_1.default(); + //_pageMenuMain = new UiPageMenuMain(); if (User_1.default.hasValidMenu()) { _pageMenuUser = new User_1.default(); } @@ -317,7 +316,7 @@ define(["require", "exports", "tslib", "../Core", "../Dom/Change/Listener", "../ function enable() { _enabled = true; if (_enableMobileMenu) { - _pageMenuMain.enable(); + //_pageMenuMain.enable(); _pageMenuUser === null || _pageMenuUser === void 0 ? void 0 : _pageMenuUser.enable(); } } @@ -337,7 +336,7 @@ define(["require", "exports", "tslib", "../Core", "../Dom/Change/Listener", "../ function disable() { _enabled = false; if (_enableMobileMenu) { - _pageMenuMain.disable(); + //_pageMenuMain.disable(); _pageMenuUser === null || _pageMenuUser === void 0 ? void 0 : _pageMenuUser.disable(); } } diff --git a/wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Page/Menu/Container.js b/wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Page/Menu/Container.js new file mode 100644 index 0000000000..a2ce3966b1 --- /dev/null +++ b/wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Page/Menu/Container.js @@ -0,0 +1,61 @@ +define(["require", "exports", "focus-trap", "../../Screen"], function (require, exports, focus_trap_1, Screen_1) { + "use strict"; + Object.defineProperty(exports, "__esModule", { value: true }); + exports.PageMenuContainer = void 0; + class PageMenuContainer { + constructor(provider) { + this.container = document.createElement("div"); + this.content = document.createElement("div"); + this.focusTrap = undefined; + this.provider = provider; + } + open() { + this.buildElements(); + this.content.innerHTML = ""; + this.content.append(this.provider.getContent()); + this.provider.getMenuButton().setAttribute("aria-expanded", "true"); + (0, Screen_1.scrollDisable)(); + this.container.hidden = false; + this.getFocusTrap().activate(); + } + close() { + this.provider.getMenuButton().setAttribute("aria-expanded", "false"); + (0, Screen_1.scrollEnable)(); + this.container.hidden = true; + this.getFocusTrap().deactivate(); + } + toggle() { + if (this.container.hidden) { + this.open(); + } + else { + this.close(); + } + } + buildElements() { + if (this.container.classList.contains("pageMenuContainer")) { + return; + } + this.container.classList.add("pageMenuContainer"); + this.container.hidden = true; + this.container.addEventListener("click", (event) => { + if (event.target === this.container) { + this.close(); + } + }); + this.content.classList.add("pageMenuContent"); + this.container.append(this.content); + document.body.append(this.container); + } + getFocusTrap() { + if (this.focusTrap === undefined) { + this.focusTrap = (0, focus_trap_1.createFocusTrap)(this.content, { + allowOutsideClick: true, + }); + } + return this.focusTrap; + } + } + exports.PageMenuContainer = PageMenuContainer; + exports.default = PageMenuContainer; +}); diff --git a/wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Page/Menu/Main.js b/wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Page/Menu/Main.js index 6ce989588b..e0cd9a1271 100644 --- a/wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Page/Menu/Main.js +++ b/wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Page/Menu/Main.js @@ -1,86 +1,160 @@ /** * Provides the touch-friendly fullscreen main menu. * - * @author Alexander Ebert - * @copyright 2001-2019 WoltLab GmbH - * @license GNU Lesser General Public License - * @module WoltLabSuite/Core/Ui/Page/Menu/Main + * @author Alexander Ebert + * @copyright 2001-2021 WoltLab GmbH + * @license GNU Lesser General Public License + * @module WoltLabSuite/Core/Ui/Page/Menu/Main */ -define(["require", "exports", "tslib", "../../../Core", "../../../Dom/Util", "../../../Language", "./Abstract"], function (require, exports, tslib_1, Core, Util_1, Language, Abstract_1) { +define(["require", "exports", "tslib", "./Container", "../../../Language", "../../../Dom/Util"], function (require, exports, tslib_1, Container_1, Language, Util_1) { "use strict"; - Core = (0, tslib_1.__importStar)(Core); - Util_1 = (0, tslib_1.__importDefault)(Util_1); + Object.defineProperty(exports, "__esModule", { value: true }); + exports.PageMenuMain = void 0; + Container_1 = (0, tslib_1.__importDefault)(Container_1); Language = (0, tslib_1.__importStar)(Language); - Abstract_1 = (0, tslib_1.__importDefault)(Abstract_1); - class UiPageMenuMain extends Abstract_1.default { - /** - * Initializes the touch-friendly fullscreen main menu. - */ + Util_1 = (0, tslib_1.__importDefault)(Util_1); + function normalizeMenuItem(menuItem) { + const anchor = menuItem.querySelector(".boxMenuLink"); + const title = anchor.querySelector(".boxMenuLinkTitle").textContent; + let counter = 0; + const outstandingItems = anchor.querySelector(".boxMenuLinkOutstandingItems"); + if (outstandingItems) { + counter = +outstandingItems.textContent.replace(/[^0-9]/, ""); + } + const subMenu = menuItem.querySelector("ol"); + let children = []; + if (subMenu instanceof HTMLOListElement) { + children = Array.from(subMenu.children).map((subMenuItem) => { + return normalizeMenuItem(subMenuItem); + }); + } + // `link.href` represents the computed link, not the raw value. + const href = anchor.getAttribute("href"); + let link = undefined; + if (href && href !== "#") { + link = anchor.href; + } + const active = menuItem.classList.contains("active"); + return { + active, + children, + counter, + link, + title, + }; + } + class PageMenuMain { constructor() { - super("com.woltlab.wcf.MainMenuMobile", "pageMainMenuMobile", "#pageHeader .mainMenu"); - this.hasItems = false; - this.title = document.getElementById("pageMainMenuMobilePageOptionsTitle"); - if (this.title !== null) { - this.navigationList = document.querySelector(".jsPageNavigationIcons"); - } - this.button.setAttribute("aria-label", Language.get("wcf.menu.page")); - this.button.setAttribute("role", "button"); + this.mainMenu = document.querySelector(".mainMenu"); + this.container = new Container_1.default(this); + this.mainMenu.addEventListener("click", (event) => { + event.preventDefault(); + this.container.toggle(); + }); } - open(event) { - if (!super.open(event)) { - return false; - } - if (this.title === null) { + getContent() { + const fragment = document.createDocumentFragment(); + fragment.append(...this.buildMainMenu()); + return fragment; + } + getMenuButton() { + return this.mainMenu; + } + buildMainMenu() { + const menu = this.mainMenu.querySelector(".boxMenu"); + const menuItems = Array.from(menu.children).map((element) => { + return normalizeMenuItem(element); + }); + const nav = document.createElement("nav"); + nav.classList.add("pageMenuMainNavigation"); + nav.setAttribute("aria-label", window.PAGE_TITLE); + nav.setAttribute("role", "navigation"); + nav.append(this.buildMenuItemList(menuItems)); + return [nav]; + } + buildMenuItemList(menuItems) { + const list = document.createElement("ul"); + list.classList.add("pageMenuMainItemList"); + menuItems + .filter((menuItem) => { + // Remove links that have no target (`#`) and do not contain any children. + if (!menuItem.link && menuItem.children.length === 0) { + return false; + } return true; - } - this.hasItems = this.navigationList && this.navigationList.childElementCount > 0; - if (this.hasItems) { - while (this.navigationList.childElementCount) { - const item = this.navigationList.children[0]; - item.classList.add("menuOverlayItem", "menuOverlayItemOption"); - item.addEventListener("click", (ev) => { - ev.stopPropagation(); - this.close(); - }); - const link = item.children[0]; - link.classList.add("menuOverlayItemLink"); - link.classList.add("box24"); - link.children[1].classList.remove("invisible"); - link.children[1].classList.add("menuOverlayItemTitle"); - this.title.insertAdjacentElement("afterend", item); + }) + .forEach((menuItem) => { + list.append(this.buildMenuItem(menuItem)); + }); + return list; + } + buildMenuItem(menuItem) { + const listItem = document.createElement("li"); + listItem.classList.add("pageMenuMainItem"); + if (menuItem.link) { + const link = document.createElement("a"); + link.classList.add("pageMenuMainItemLink"); + link.href = menuItem.link; + link.textContent = menuItem.title; + if (menuItem.active) { + link.setAttribute("aria-current", "page"); } - Util_1.default.show(this.title); + listItem.append(link); } else { - Util_1.default.hide(this.title); + const label = document.createElement("span"); + label.textContent = menuItem.title; + listItem.append(label); } - return true; + if (menuItem.children.length) { + listItem.classList.add("pageMenuMainItemExpandable"); + const menuId = Util_1.default.getUniqueId(); + const button = document.createElement("a"); + button.classList.add("pageMenuMainItemToggle"); + button.tabIndex = 0; + button.setAttribute("role", "button"); + button.setAttribute("aria-expanded", "false"); + button.setAttribute("aria-controls", menuId); + button.setAttribute("aria-label", Language.get("TODO")); + button.innerHTML = ''; + const list = this.buildMenuItemList(menuItem.children); + list.id = menuId; + list.hidden = true; + button.addEventListener("click", (event) => { + event.preventDefault(); + this.toggleList(button, list); + }); + button.addEventListener("keydown", (event) => { + if (event.key === "Enter" || event.key === " ") { + event.preventDefault(); + button.click(); + } + }); + list.addEventListener("keydown", (event) => { + if (event.key === "Escape") { + event.preventDefault(); + event.stopPropagation(); + this.toggleList(button, list); + } + }); + listItem.append(button, list); + } + return listItem; } - close(event) { - if (!super.close(event)) { - return false; + toggleList(button, list) { + if (list.hidden) { + button.setAttribute("aria-expanded", "true"); + list.hidden = false; } - if (this.hasItems) { - Util_1.default.hide(this.title); - let item = this.title.nextElementSibling; - while (item && item.classList.contains("menuOverlayItemOption")) { - item.classList.remove("menuOverlayItem", "menuOverlayItemOption"); - item.removeEventListener("click", (ev) => { - ev.stopPropagation(); - this.close(); - }); - const link = item.children[0]; - link.classList.remove("menuOverlayItemLink"); - link.classList.remove("box24"); - link.children[1].classList.add("invisible"); - link.children[1].classList.remove("menuOverlayItemTitle"); - this.navigationList.appendChild(item); - item = item.nextElementSibling; + else { + button.setAttribute("aria-expanded", "false"); + list.hidden = true; + if (document.activeElement !== button) { + button.focus(); } } - return true; } } - Core.enableLegacyInheritance(UiPageMenuMain); - return UiPageMenuMain; + exports.PageMenuMain = PageMenuMain; + exports.default = PageMenuMain; }); diff --git a/wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Page/Menu/Provider.js b/wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Page/Menu/Provider.js new file mode 100644 index 0000000000..2ae92b6a8b --- /dev/null +++ b/wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Page/Menu/Provider.js @@ -0,0 +1,4 @@ +define(["require", "exports"], function (require, exports) { + "use strict"; + Object.defineProperty(exports, "__esModule", { value: true }); +});