From: Alexander Ebert Date: Mon, 13 Dec 2021 13:35:20 +0000 (+0100) Subject: Draft for the layout of the user menu X-Git-Tag: 5.5.0_Alpha_1~242^2~43 X-Git-Url: https://git.stricted.de/?a=commitdiff_plain;h=77e970f49eb07f3977983f4439dc2f8c2a67e7ef;p=GitHub%2FWoltLab%2FWCF.git Draft for the layout of the user menu --- diff --git a/ts/WoltLabSuite/Core/Ui/Mobile.ts b/ts/WoltLabSuite/Core/Ui/Mobile.ts index d1821f9301..d749270a90 100644 --- a/ts/WoltLabSuite/Core/Ui/Mobile.ts +++ b/ts/WoltLabSuite/Core/Ui/Mobile.ts @@ -15,7 +15,7 @@ import * as UiAlignment from "./Alignment"; import UiCloseOverlay from "./CloseOverlay"; import * as UiDropdownReusable from "./Dropdown/Reusable"; import { PageMenuMain } from "./Page/Menu/Main"; -import UiPageMenuUser from "./Page/Menu/User"; +import { hasValidUserMenu, PageMenuUser } from "./Page/Menu/User"; import * as UiScreen from "./Screen"; interface MainMenuMorePayload { @@ -31,7 +31,7 @@ let _enableMobileMenu = false; const _knownMessages = new WeakSet(); let _mobileSidebarEnabled = false; let _pageMenuMain: PageMenuMain; -let _pageMenuUser: UiPageMenuUser | undefined = undefined; +let _pageMenuUser: PageMenuUser | undefined = undefined; let _messageGroups: HTMLCollection | null = null; const _sidebars: HTMLElement[] = []; @@ -166,8 +166,9 @@ function initMobileMenu(): void { _pageMenuMain = new PageMenuMain(); _pageMenuMain.enable(); - if (UiPageMenuUser.hasValidMenu()) { - _pageMenuUser = new UiPageMenuUser(); + if (hasValidUserMenu()) { + _pageMenuUser = new PageMenuUser(); + _pageMenuUser.enable(); } } } diff --git a/ts/WoltLabSuite/Core/Ui/Page/Menu/Main.ts b/ts/WoltLabSuite/Core/Ui/Page/Menu/Main.ts index e9e6b9934b..0b5532dc65 100644 --- a/ts/WoltLabSuite/Core/Ui/Page/Menu/Main.ts +++ b/ts/WoltLabSuite/Core/Ui/Page/Menu/Main.ts @@ -1,5 +1,5 @@ /** - * Provides the touch-friendly fullscreen main menu. + * Provides the touch-friendly main menu. * * @author Alexander Ebert * @copyright 2001-2021 WoltLab GmbH diff --git a/ts/WoltLabSuite/Core/Ui/Page/Menu/User.ts b/ts/WoltLabSuite/Core/Ui/Page/Menu/User.ts index 1efcc28777..a1deaf16a4 100644 --- a/ts/WoltLabSuite/Core/Ui/Page/Menu/User.ts +++ b/ts/WoltLabSuite/Core/Ui/Page/Menu/User.ts @@ -1,89 +1,126 @@ /** - * Provides the touch-friendly fullscreen user menu. + * Provides the touch-friendly user menu. * - * @author Alexander Ebert - * @copyright 2001-2019 WoltLab GmbH - * @license GNU Lesser General Public License - * @module WoltLabSuite/Core/Ui/Page/Menu/User + * @author Alexander Ebert + * @copyright 2001-2021 WoltLab GmbH + * @license GNU Lesser General Public License + * @module WoltLabSuite/Core/Ui/Page/Menu/User */ -import * as Core from "../../../Core"; -import * as EventHandler from "../../../Event/Handler"; +import PageMenuContainer from "./Container"; +import { PageMenuProvider } from "./Provider"; import * as Language from "../../../Language"; -import UiPageMenuAbstract from "./Abstract"; +import { getUserMenuProviders } from "../../User/Menu/Manager"; +import { UserMenuProvider } from "../../User/Menu/Data/Provider"; +import DomUtil from "../../../Dom/Util"; -interface EventPayload { - count: number; - identifier: string; -} +type CallbackOpen = (event: MouseEvent) => void; + +type Tab = HTMLAnchorElement; +type TabPanel = HTMLDivElement; +type TabComponents = [Tab, TabPanel]; + +type TabList = HTMLDivElement; +type TabPanelContainer = HTMLDivElement; +type TabMenu = [TabList, TabPanelContainer]; + +export class PageMenuUser implements PageMenuProvider { + private readonly callbackOpen: CallbackOpen; + private readonly container: PageMenuContainer; + private readonly userMenu: HTMLElement; -class UiPageMenuUser extends UiPageMenuAbstract { - /** - * Initializes the touch-friendly fullscreen user menu. - */ constructor() { - super("com.woltlab.wcf.UserMenuMobile", "pageUserMenuMobile", "#pageHeader .userPanel"); + this.userMenu = document.querySelector(".userPanel")!; + + this.container = new PageMenuContainer(this); - EventHandler.add("com.woltlab.wcf.userMenu", "updateBadge", (data) => this.updateBadge(data)); + this.callbackOpen = (event) => { + event.preventDefault(); + event.stopPropagation(); - this.button.setAttribute("aria-label", Language.get("wcf.menu.user")); - this.button.setAttribute("role", "button"); + this.container.toggle(); + }; } - close(event?: Event): boolean { - // The user menu is not initialized if there are no items to display. - if (this.menu === undefined) { - return false; - } + enable(): void { + this.userMenu.setAttribute("aria-expanded", "false"); + this.userMenu.setAttribute("role", "button"); + this.userMenu.tabIndex = 0; + this.userMenu.addEventListener("click", this.callbackOpen); + } - const dropdown = window.WCF.Dropdown.Interactive.Handler.getOpenDropdown(); - if (dropdown) { - if (event) { - event.preventDefault(); - event.stopPropagation(); - } + disable(): void { + this.container.close(); - dropdown.close(); + this.userMenu.removeAttribute("aria-expanded"); + this.userMenu.removeAttribute("role"); + this.userMenu.removeAttribute("tabindex"); + this.userMenu.removeEventListener("click", this.callbackOpen); + } - return true; - } + getContent(): DocumentFragment { + const fragment = document.createDocumentFragment(); + fragment.append(...this.buildTabMenu()); - return super.close(event); + return fragment; } - private updateBadge(data: EventPayload): void { - this.menu.querySelectorAll(".menuOverlayItemBadge").forEach((item: HTMLElement) => { - if (item.dataset.badgeIdentifier === data.identifier) { - let badge = item.querySelector(".badge"); - if (data.count) { - if (badge === null) { - badge = document.createElement("span"); - badge.className = "badge badgeUpdate"; - item.appendChild(badge); - } - - badge.textContent = data.count.toString(); - } else if (badge !== null) { - badge.remove(); - } - - this.updateButtonState(); - } - }); + getMenuButton(): HTMLElement { + return this.userMenu; } - static hasValidMenu(): boolean { - const menu = document.querySelector("#pageUserMenuMobile > .menuOverlayItemList")!; - if (menu.childElementCount === 1 && menu.children[0].classList.contains("menuOverlayTitle")) { - const userPanel = document.querySelector("#pageHeader .userPanel")!; - userPanel.classList.add("hideUserPanel"); - return false; - } + private buildTabMenu(): TabMenu { + const tabList = document.createElement("div"); + tabList.classList.add("pageMenuUserTabList"); + tabList.setAttribute("role", "tablist"); + tabList.setAttribute("aria-label", Language.get("TODO")); + + const tabPanelContainer = document.createElement("div"); - return true; + // TODO: Inject the control panel first. + + getUserMenuProviders().forEach((provider) => { + const [tab, tabPanel] = this.buildTab(provider); + + tabList.append(tab); + tabPanelContainer.append(tabPanel); + }); + + // TODO: Inject legacy user panel items. + + return [tabList, tabPanelContainer]; + } + + private buildTab(provider: UserMenuProvider): TabComponents { + const tabId = DomUtil.getUniqueId(); + const panelId = DomUtil.getUniqueId(); + + const tab = document.createElement("a"); + tab.classList.add("pageMenuUserTab"); + tab.id = tabId; + tab.setAttribute("aria-controls", panelId); + tab.setAttribute("aria-selected", "false"); + tab.setAttribute("role", "tab"); + tab.tabIndex = -1; + + const button = provider.getPanelButton().querySelector("a")!; + tab.setAttribute("aria-label", button.dataset.title || button.title); + tab.innerHTML = button.querySelector(".icon")!.outerHTML; + + const panel = document.createElement("div"); + panel.classList.add("pageMenuUserTabPanel"); + panel.id = panelId; + panel.hidden = true; + panel.setAttribute("aria-labelledby", tabId); + panel.setAttribute("role", "tabpanel"); + panel.tabIndex = 0; + + return [tab, panel]; } } -Core.enableLegacyInheritance(UiPageMenuUser); +export function hasValidUserMenu(): boolean { + return true; +} -export = UiPageMenuUser; +export default PageMenuUser; diff --git a/ts/WoltLabSuite/Core/Ui/User/Menu/Manager.ts b/ts/WoltLabSuite/Core/Ui/User/Menu/Manager.ts index f5fb67413e..5864b7154f 100644 --- a/ts/WoltLabSuite/Core/Ui/User/Menu/Manager.ts +++ b/ts/WoltLabSuite/Core/Ui/User/Menu/Manager.ts @@ -94,6 +94,10 @@ function getView(provider: UserMenuProvider): UserMenuView { return views.get(provider)!; } +export function getUserMenuProviders(): Set { + return providers; +} + export function getContainer(): HTMLElement { if (container === undefined) { container = document.createElement("div"); diff --git a/wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Mobile.js b/wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Mobile.js index 112d6b1785..8d7a596abf 100644 --- a/wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Mobile.js +++ b/wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Mobile.js @@ -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); - User_1 = (0, tslib_1.__importDefault)(User_1); UiScreen = (0, tslib_1.__importStar)(UiScreen); let _dropdownMenu = null; let _dropdownMenuMessage = null; @@ -138,8 +137,9 @@ define(["require", "exports", "tslib", "../Core", "../Dom/Change/Listener", "../ if (_enableMobileMenu) { _pageMenuMain = new Main_1.PageMenuMain(); _pageMenuMain.enable(); - if (User_1.default.hasValidMenu()) { - _pageMenuUser = new User_1.default(); + if ((0, User_1.hasValidUserMenu)()) { + _pageMenuUser = new User_1.PageMenuUser(); + _pageMenuUser.enable(); } } } 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 923ca0608d..b1fc6bf777 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,5 +1,5 @@ /** - * Provides the touch-friendly fullscreen main menu. + * Provides the touch-friendly main menu. * * @author Alexander Ebert * @copyright 2001-2021 WoltLab GmbH diff --git a/wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Page/Menu/User.js b/wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Page/Menu/User.js index 4027977d91..6692db57e0 100644 --- a/wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Page/Menu/User.js +++ b/wcfsetup/install/files/js/WoltLabSuite/Core/Ui/Page/Menu/User.js @@ -1,72 +1,91 @@ /** - * Provides the touch-friendly fullscreen user menu. + * Provides the touch-friendly user menu. * - * @author Alexander Ebert - * @copyright 2001-2019 WoltLab GmbH - * @license GNU Lesser General Public License - * @module WoltLabSuite/Core/Ui/Page/Menu/User + * @author Alexander Ebert + * @copyright 2001-2021 WoltLab GmbH + * @license GNU Lesser General Public License + * @module WoltLabSuite/Core/Ui/Page/Menu/User */ -define(["require", "exports", "tslib", "../../../Core", "../../../Event/Handler", "../../../Language", "./Abstract"], function (require, exports, tslib_1, Core, EventHandler, Language, Abstract_1) { +define(["require", "exports", "tslib", "./Container", "../../../Language", "../../User/Menu/Manager", "../../../Dom/Util"], function (require, exports, tslib_1, Container_1, Language, Manager_1, Util_1) { "use strict"; - Core = (0, tslib_1.__importStar)(Core); - EventHandler = (0, tslib_1.__importStar)(EventHandler); + Object.defineProperty(exports, "__esModule", { value: true }); + exports.hasValidUserMenu = exports.PageMenuUser = 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 UiPageMenuUser extends Abstract_1.default { - /** - * Initializes the touch-friendly fullscreen user menu. - */ + Util_1 = (0, tslib_1.__importDefault)(Util_1); + class PageMenuUser { constructor() { - super("com.woltlab.wcf.UserMenuMobile", "pageUserMenuMobile", "#pageHeader .userPanel"); - EventHandler.add("com.woltlab.wcf.userMenu", "updateBadge", (data) => this.updateBadge(data)); - this.button.setAttribute("aria-label", Language.get("wcf.menu.user")); - this.button.setAttribute("role", "button"); + this.userMenu = document.querySelector(".userPanel"); + this.container = new Container_1.default(this); + this.callbackOpen = (event) => { + event.preventDefault(); + event.stopPropagation(); + this.container.toggle(); + }; } - close(event) { - // The user menu is not initialized if there are no items to display. - if (this.menu === undefined) { - return false; - } - const dropdown = window.WCF.Dropdown.Interactive.Handler.getOpenDropdown(); - if (dropdown) { - if (event) { - event.preventDefault(); - event.stopPropagation(); - } - dropdown.close(); - return true; - } - return super.close(event); + enable() { + this.userMenu.setAttribute("aria-expanded", "false"); + this.userMenu.setAttribute("role", "button"); + this.userMenu.tabIndex = 0; + this.userMenu.addEventListener("click", this.callbackOpen); } - updateBadge(data) { - this.menu.querySelectorAll(".menuOverlayItemBadge").forEach((item) => { - if (item.dataset.badgeIdentifier === data.identifier) { - let badge = item.querySelector(".badge"); - if (data.count) { - if (badge === null) { - badge = document.createElement("span"); - badge.className = "badge badgeUpdate"; - item.appendChild(badge); - } - badge.textContent = data.count.toString(); - } - else if (badge !== null) { - badge.remove(); - } - this.updateButtonState(); - } + disable() { + this.container.close(); + this.userMenu.removeAttribute("aria-expanded"); + this.userMenu.removeAttribute("role"); + this.userMenu.removeAttribute("tabindex"); + this.userMenu.removeEventListener("click", this.callbackOpen); + } + getContent() { + const fragment = document.createDocumentFragment(); + fragment.append(...this.buildTabMenu()); + return fragment; + } + getMenuButton() { + return this.userMenu; + } + buildTabMenu() { + const tabList = document.createElement("div"); + tabList.classList.add("pageMenuUserTabList"); + tabList.setAttribute("role", "tablist"); + tabList.setAttribute("aria-label", Language.get("TODO")); + const tabPanelContainer = document.createElement("div"); + // TODO: Inject the control panel first. + (0, Manager_1.getUserMenuProviders)().forEach((provider) => { + const [tab, tabPanel] = this.buildTab(provider); + tabList.append(tab); + tabPanelContainer.append(tabPanel); }); + // TODO: Inject legacy user panel items. + return [tabList, tabPanelContainer]; } - static hasValidMenu() { - const menu = document.querySelector("#pageUserMenuMobile > .menuOverlayItemList"); - if (menu.childElementCount === 1 && menu.children[0].classList.contains("menuOverlayTitle")) { - const userPanel = document.querySelector("#pageHeader .userPanel"); - userPanel.classList.add("hideUserPanel"); - return false; - } - return true; + buildTab(provider) { + const tabId = Util_1.default.getUniqueId(); + const panelId = Util_1.default.getUniqueId(); + const tab = document.createElement("a"); + tab.classList.add("pageMenuUserTab"); + tab.id = tabId; + tab.setAttribute("aria-controls", panelId); + tab.setAttribute("aria-selected", "false"); + tab.setAttribute("role", "tab"); + tab.tabIndex = -1; + const button = provider.getPanelButton().querySelector("a"); + tab.setAttribute("aria-label", button.dataset.title || button.title); + tab.innerHTML = button.querySelector(".icon").outerHTML; + const panel = document.createElement("div"); + panel.classList.add("pageMenuUserTabPanel"); + panel.id = panelId; + panel.hidden = true; + panel.setAttribute("aria-labelledby", tabId); + panel.setAttribute("role", "tabpanel"); + panel.tabIndex = 0; + return [tab, panel]; } } - Core.enableLegacyInheritance(UiPageMenuUser); - return UiPageMenuUser; + exports.PageMenuUser = PageMenuUser; + function hasValidUserMenu() { + return true; + } + exports.hasValidUserMenu = hasValidUserMenu; + exports.default = PageMenuUser; }); diff --git a/wcfsetup/install/files/js/WoltLabSuite/Core/Ui/User/Menu/Manager.js b/wcfsetup/install/files/js/WoltLabSuite/Core/Ui/User/Menu/Manager.js index f67fe06d01..2552cfc0e6 100644 --- a/wcfsetup/install/files/js/WoltLabSuite/Core/Ui/User/Menu/Manager.js +++ b/wcfsetup/install/files/js/WoltLabSuite/Core/Ui/User/Menu/Manager.js @@ -10,7 +10,7 @@ define(["require", "exports", "tslib", "../../Alignment", "../../CloseOverlay", "../../../Event/Handler", "../../../Dom/Util"], function (require, exports, tslib_1, Alignment, CloseOverlay_1, EventHandler, Util_1) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); - exports.registerProvider = exports.getContainer = void 0; + exports.registerProvider = exports.getContainer = exports.getUserMenuProviders = void 0; Alignment = (0, tslib_1.__importStar)(Alignment); CloseOverlay_1 = (0, tslib_1.__importDefault)(CloseOverlay_1); EventHandler = (0, tslib_1.__importStar)(EventHandler); @@ -75,6 +75,10 @@ define(["require", "exports", "tslib", "../../Alignment", "../../CloseOverlay", } return views.get(provider); } + function getUserMenuProviders() { + return providers; + } + exports.getUserMenuProviders = getUserMenuProviders; function getContainer() { if (container === undefined) { container = document.createElement("div");